正如您在此代码中看到的那样:
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
次迭代。我该如何取消?
答案 0 :(得分:25)
使用接受Task.Delay
CancellationToken
overload
public async void TaskDelayTest(CancellationToken token)
{
while (LoopCheck)
{
for (int i = 0; i < 100; i++)
{
textBox1.Text = i.ToString();
await Task.Delay(1000, token);
}
}
}
...
var tokenSource = new CancellationTokenSource();
TaskDelayTest(tokenSource.Token);
...
// cancelling
LoopCheck = false;
tokenSource.Cancel();
答案 1 :(得分:8)
如果您要进行投票,请在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);
}
}
有关详细信息,请参阅cancellation documentation。
答案 2 :(得分:4)
关于拥有取消令牌并使用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
}
}
}
}
答案 3 :(得分:0)
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 :(得分:-1)
遇到这个问题后,我写了一个替代品,如果你想进行轮询,它会按预期运行:
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));
}
答案 5 :(得分:-2)
这可能是一个非常愚蠢和基本的解决方案,但我的想法是......
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;
}
}