C# 在循环中添加一个短延迟可以防止它无限期地循环。为什么?
在使用.NET async/await API时,我遇到了一个奇怪的问题:循环忽略了用作超时的延迟,直到我在循环中添加了一个短延迟。这是怎么回事?不是最直观的行为 完整程序:C# 在循环中添加一个短延迟可以防止它无限期地循环。为什么?,c#,asynchronous,async-await,task,infinite-loop,C#,Asynchronous,Async Await,Task,Infinite Loop,在使用.NET async/await API时,我遇到了一个奇怪的问题:循环忽略了用作超时的延迟,直到我在循环中添加了一个短延迟。这是怎么回事?不是最直观的行为 完整程序: using System; using System.Threading.Tasks; public class Program { public static void Main(String[] args) { Task.Run(async () => {
using System;
using System.Threading.Tasks;
public class Program
{
public static void Main(String[] args)
{
Task.Run(async () =>
{
await Task.WhenAny(Loop(), Task.Delay(TimeSpan.FromSeconds(1)));
Console.WriteLine("Timed out!");
})
.Wait();
}
public static async Task Loop()
{
while(true)
{
// Commenting this out makes the code loop indefinitely!
await Task.Delay(TimeSpan.FromMilliseconds(1));
// This doesn't matter.
await DoWork();
}
}
public static async Task DoWork()
{
await Task.CompletedTask;
}
}
背景
实际程序在(!done)时已执行,但由于错误,done
从未设置为true
。循环使许多等待调用。任务。当单元测试中有任何调用以防止Loop()
挂起时。如果我在大多数时候故意引入一个bug,测试确实超时了,但有时它仍然挂起
不需要任务的建议解决方法。在循环()中延迟
bool completedOnTime = Task.Run(() => Loop()).Wait(TimeSpan.FromSeconds(1));
这将执行Loop()
方法
相关问题
您当前的循环()
任务将永远与您的循环,而(true)
条件:
public static async Task Loop()
{
while(true) { } // this iteration will never end.
// as noted by Scott Chamberlain's answer, the caller will
// never regain control of this task
}
您应该考虑通过<代码>取消EngultToC/<代码>来中断您的循环。< /P>
public static async Task Loop(CancellationTokenSource cts)
{
while (cts != null && !cts.IsCancellationRequested)
{
// your work you want to keep iterating until cancelled
}
}
我从中借来帮助解释,这也符合我的建议:
当第一个任务完成时,考虑是否取消
剩余任务。如果其他任务未取消,但同时
从来没有等待过,那么他们就被抛弃了放弃的任务将运行到
完成后,其结果将被忽略。中的任何例外情况
那些被放弃的任务也将被忽略
其他资源:当您等待任务时,它首先检查任务是否完成,如果任务完成,它只会继续执行,而不会返回调用方。因此,调用wait DoWork()
永远不会使您返回调用方法,它只会在方法中同步继续
当您删除延迟时,您现在就拥有了
public static async Task Loop()
{
while(true)
{
}
}
因此,循环将永远循环,而不会将控制权返回给调用方。在这种情况下,如果您不知道是否将返回调用方,并且希望保证不会永远循环,那么您可以将代码重写为
public static async Task Loop()
{
while(true)
{
var workTask = DoWork();
if(workTask.GetAwaiter().IsCompleted) //This IsCompleted property is the thing that determines if the code will be synchronous.
await Task.Yield(); //If we where syncronous force a return here via the yield.
await workTask; //We still await the task here in case where where not complete, also to observe any exceptions.
}
}
有些事情看起来不对劲,您是否考虑过在超时任务完成时使用CancellationToken
停止任务循环()?这里的代码闻起来很奇怪,因为您的任务.Loop()
永远不会停止,而且我看不到一个明确的方法来停止它。您是对的,在需要任何东西的时候实际停止它是一个取消令牌。然而,问题是,我们永远没有机会发出取消请求。这是因为wait wheny将永远不会返回。@KonradJamrozik实际上问题更严重,如果将该调用移动到var loopTask=Loop(),则永远不会返回的是Loop
,而不是WaitAny
;wait Task.WhenAny(loopTask,Task.Delay(TimeSpan.FromSeconds(1)))代码>您可以在调试器中看到它。@Svek我认为取消可能有副作用的任务(控制台或文件写入,或其他一些可见的效果)不是一个好主意。假设循环条件实际上是“while(!done)”,但由于错误,“done”从未设置为true。WhenAny超时用于测试中,以防止测试在出现错误时挂起。在这种情况下,添加取消令牌检查将相当于用测试代码污染生产代码:(@Svek,但添加取消令牌源只是为了防止bug。这不是一个干净的设计。我不会反对断言,但我发现很难证明cts只是“以防万一”的,不是所需业务逻辑的一部分。@KonradJamrozik如果您的目的不是停止迭代,请帮助我了解您试图完成的任务?您可以使用Thread.Sleep()模拟“长时间运行的流程”
,但您的问题指出该方法称为循环
而不是SampleLongProcess
,我希望我是有道理的。@KonradJamrozik-我明白您关于使用cts
的观点,因为它可能被理解为用于中断迭代的错误方法。值得注意的是……您可以很容易地加入自己的论点,例如作为done
而不是传入cts
。对于一个含糊不清的情况,要想找到一个完整的答案有点困难。