[英]How to cancel await 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 它仍然會異步執行它所做的事情。
當我設置LoopCheck=false
時,我想取消所有等待的Task.Delay()
迭代。 我怎樣才能取消它?
使用接受CancellationToken
的Task.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();
如果您要進行投票,請對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);
}
}
有關更多信息,請參閱取消文檔。
只是關於取消令牌的輕微評論,並使用 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
}
}
}
}
遇到這個問題后,我寫了一個替換的下降,如果你想進行輪詢,它的行為會像預期的那樣:
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);
}
}
它使用取消標記回調來完成任務,然后等待該合成任務或沒有取消標記的普通 Task.Delay。 這樣當源令牌被取消時它不會拋出異常,但仍然通過返回執行來響應取消。 您仍然需要在調用 IsCancellationRequested 后檢查它以決定如果它被取消該怎么做。
單元測試,如果有人感興趣:
[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));
}
這可能是一個非常愚蠢和基本的解決方案,但我的想法是......
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;
}
}
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.