Как отменить ожидание Task.Delay()?

Как вы можете видеть в этом коде:

public async void TaskDelayTest()
{
     while (LoopCheck)
     {
          for (int i = 0; i < 100; i++)
          {
               textBox1.Text = i.ToString();
               await Task.Delay(1000);
          }
     }
}

Я хочу, чтобы он устанавливал текстовое поле в строковое значение i с одним вторым периодом, пока не установил значение LoopCheck значение false. Но что он делает, так это то, что он создает все итерации для всех, и даже если я устанавливаю значение LoopCheck в false, он все равно делает то, что он делает асинхронно.

Я хочу отменить всю ожидаемую Task.Delay() когда я устанавливаю LoopCheck=false. Как я могу отменить его?

Ответ 1

Используйте перегрузку из Task.Delay, которая принимает CancellationToken

public async void TaskDelayTest(CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        for (int i = 0; i < 100; i++)
        {
            textBox1.Text = i.ToString();
            await Task.Delay(1000, token);
        }
    }
}

var tokenSource = new CancellationTokenSource();
TaskDelayTest(tokenSource.Token);
...
tokenSource.Cancel();

Ответ 2

Если вы собираетесь опросить, опрос на CancellationToken:

public async Task TaskDelayTestAsync(CancellationToken token)
{
  for (int i = 0; i < 100; i++)
  {
    textBox1.Text = i.ToString();
    await Task.Delay(TimeSpan.FromSeconds(1), token);
  }
}

Для получения дополнительной информации см. Документацию об аннулировании.

Ответ 3

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static DateTime start;
        static CancellationTokenSource tokenSource;
        static void Main(string[] args)
        {
            start = DateTime.Now;
            Console.WriteLine(start);


            TaskDelayTest();

            TaskCancel();

            Console.ReadKey();
        }

        public static async void TaskCancel()
        {
            await Task.Delay(3000);

            tokenSource?.Cancel();

            DateTime end = DateTime.Now;
            Console.WriteLine(end);
            Console.WriteLine((end - start).TotalMilliseconds);
        }

        public static async void TaskDelayTest()
        {
            tokenSource = new CancellationTokenSource();

            try
            {
                await Task.Delay(2000, tokenSource.Token);
                DateTime end = DateTime.Now;
                Console.WriteLine(end);
                Console.WriteLine((end - start).TotalMilliseconds);
            }
            catch (TaskCanceledException ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                tokenSource.Dispose();
                tokenSource = null;
            }
        }
    }
}

Ответ 4

Это может быть очень глупое и элементарное решение, но моя идея...

public async void TaskDelayTest()
{
     while (LoopCheck)
     {
          for (int i = 0; i < 100; i++)
          {
               textBox1.Text = i.ToString();
               await Delay(1000);
          }
     }
}

private async void Delay(int delayInMillisecond)
{
    for(int i=0; i<delayInMillisecond; i++)
    {
        await Task.Delay(1)
        if(!LoopCheck)
            break;
    }
}

Преимущество такого подхода состоит в том, что вам нужно установить LoopCheck в false чтобы достичь своей цели, в то время как другие подходы требуют одновременного использования CancellationTokenSource.Cancel(). Да, это займет всего 1 миллисекунду, чтобы выйти из цикла, но никто этого не заметил.

Если вы хотите, чтобы ваша задержка была более точной, попробуйте следующий метод.

public async void TaskDelayTest()
{
     while (LoopCheck)
     {
          for (int i = 0; i < 100; i++)
          {
               textBox1.Text = i.ToString();
               await Task.Run(()=>Delay(1000));
          }
     }
}

private void Delay(int delayInMillisecond)
{
    double delayInSec = (double) delayInMillisecond / 1000;
    var sw = new Stopwatch();
    sw.Start();
    while(true){
        double ticks = sw.ElapsedTicks;
        double seconds = ticks / Stopwatch.Frequency;
        if(seconds >= delayInSec || !LoopCheck)
            break;
    }
}