简体   繁体   English

如何取消等待 Task.Delay()?

[英]How to cancel await Task.Delay()?

As you can see in this code:正如您在此代码中看到的:

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

I want it to set textbox to string value of i with one second period until I set LoopCheck value to false .我希望它以一秒钟的时间将文本框设置为i字符串值,直到我将LoopCheck值设置为false But what it does is that it creates all iteration ones in for all and even if I set LoopCheck value to false it still does what it does asyncronously.但它所做的是为所有人创建所有迭代,即使我将 LoopCheck 值设置为 false 它仍然会异步执行它所做的事情。

I want to cancel all awaited Task.Delay() iteration when I set LoopCheck=false .当我设置LoopCheck=false时,我想取消所有等待的Task.Delay()迭代。 How can I cancel it?我怎样才能取消它?

Use the overload of Task.Delay which accepts a CancellationToken使用接受CancellationTokenTask.Delay重载

public async Task TaskDelayTest(CancellationToken token)
{
    while (LoopCheck)
    {
        token.throwIfCancellationRequested();
        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();

If you're going to poll, poll on a CancellationToken :如果您要进行投票,请对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);
  }
}

For more information, see the cancellation documentation .有关更多信息,请参阅取消文档

Just a slight comment about having a cancellation token, and using a try-catch to stop it throwing an exception - your iteration block might fail due to a different reason, or it might fail due to a different task getting cancelled (eg from an http request timing out in a sub method), so to have the cancellation token not throw an exception you might want a bit more complicated catch block只是关于取消令牌的轻微评论,并使用 try-catch 来阻止它抛出异常 - 您的迭代块可能由于不同的原因而失败,或者由于不同的任务被取消(例如来自 http请求在子方法中超时),因此为了让取消令牌不抛出异常,您可能需要更复杂的 catch 块

public async void TaskDelayTest(CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        for (int i = 0; i < 100; i++)
        {
            try
            {
                textBox1.Text = i.ToString();
                await DoSomethingThatMightFail();
                await Task.Delay(1000, token);
            }
            catch (OperationCanceledException) when (token.IsCancellationRequested)
            {
                //task is cancelled, return or do something else
                return;
            }
            catch(Exception ex)
            {
                 //this is an actual error, log/throw/dostuff here
            }
        }
    }
}

After running into this problem I wrote a drop in replacement that behaves as expected if you want to do polling:遇到这个问题后,我写了一个替换的下降,如果你想进行轮询,它的行为会像预期的那样:

public static class TaskDelaySafe
{
    public static async Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
    {
        await Task.Delay(TimeSpan.FromMilliseconds(millisecondsDelay), cancellationToken);
    }

    public static async Task Delay(TimeSpan delay, CancellationToken cancellationToken)
    {
        var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        var task = new TaskCompletionSource<int>();

        tokenSource.Token.Register(() => task.SetResult(0));

        await Task.WhenAny(
            Task.Delay(delay, CancellationToken.None),
            task.Task);
    }
}

It uses a cancellation token callback to complete a task and then awaits either that synthetic task or the normal Task.Delay with no cancellation token.它使用取消标记回调来完成任务,然后等待该合成任务或没有取消标记的普通 Task.Delay。 This way it won't throw an exception when the source token is cancelled, but still responds to the cancellation by returning execution.这样当源令牌被取消时它不会抛出异常,但仍然通过返回执行来响应取消。 You still need to check the IsCancellationRequested after calling it to decide what to do if it is cancelled.您仍然需要在调用 IsCancellationRequested 后检查它以决定如果它被取消该怎么做。

Unit tests, if anyone is interested:单元测试,如果有人感兴趣:

    [Test]
    public async Task TaskDelay_WaitAlongTime()
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();
        await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), System.Threading.CancellationToken.None);
        Assert.IsTrue(sw.Elapsed > System.TimeSpan.FromSeconds(4));
    }

    [Test]
    public async Task TaskDelay_DoesNotWaitAlongTime()
    {
        var tokenSource = new System.Threading.CancellationTokenSource(250);

        var sw = System.Diagnostics.Stopwatch.StartNew();
        await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), tokenSource.Token);
        Assert.IsTrue(sw.Elapsed < System.TimeSpan.FromSeconds(1));
    }

    [Test]
    public async Task TaskDelay_PrecancelledToken()
    {
        var tokenSource = new System.Threading.CancellationTokenSource();
        tokenSource.Cancel();

        var sw = System.Diagnostics.Stopwatch.StartNew();
        await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), tokenSource.Token);
        Assert.IsTrue(sw.Elapsed < System.TimeSpan.FromSeconds(1));
    }

This might be a very stupid and elementary solution, but my idea is... 这可能是一个非常愚蠢和基本的解决方案,但我的想法是......

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;
    }
}

The advantage of this approach is you only have to set LoopCheck to false to achieve your goal while other approaches require to use CancellationTokenSource.Cancel() at the same time. 这种方法的优点是您只需将LoopCheck设置为false即可实现您的目标,而其他方法则需要同时使用CancellationTokenSource.Cancel() Yes, this does take 1 millisecond at most to get out of the loop but nobody can notice that. 是的,这最多需要1毫秒来摆脱循环,但没有人能注意到这一点。

If you want your delay to be more accurate, try the following method. 如果您希望延迟更准确,请尝试以下方法。

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;
    }
}
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;
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM