Task.WaitAll不等待任务完成

在试图弄清楚新的时候(也许现在不是那么新,但对我而言是新的)C#中的Task异步编程,我遇到了一个问题,让我有点想弄清楚,我不知道为什么。

我已经解决了这个问题,但我仍然不确定为什么这是一个问题。 我以为我会分享我的经验,以防任何人遇到同样的情况。

如果有任何大师想告诉我这个问题的原因,那就太棒了,非常感激。 我总是喜欢知道为什么有些东西不起作用!

我做了一个测试任务,如下:

 Random rng = new Random((int)DateTime.UtcNow.Ticks); int delay = rng.Next(1500, 15000); Task<Task> testTask = Task.Factory.StartNew<Task>( async (obj) => { DateTime startTime = DateTime.Now; Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj); await Task.Delay((int)obj); Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); return obj; }, delay ); Task<Task>[] tasks = new Task<Task>[] { testTask }; Task.WaitAll(tasks); Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); // make console stay open till user presses enter Console.ReadLine(); 

然后我运行应用程序以查看它吐出的内容。 这是一些示例输出:

6:06:15.5661 – 启动延迟3053ms的测试任务。
6:06:15.5662 – 等待结束。
6:06:18.5743 – 测试任务在3063.235ms之后完成。

如您所见, Task.WaitAll(tasks); 声明没有做太多。 在继续执行之前,它总共等待了1毫秒。

我已经在下面回答了我自己的“问题” – 但正如我上面所说 – 如果有更多知识渊博的人会解释为什么这不起作用,请做! (我认为一旦它到达await运算符,它可能与该方法的执行’踩出’有关 – 然后在等待完成后退回……但我可能错了)

您应该避免将Task.Factory.StartNew与async-await一起使用。 您应该使用Task.Run

异步方法返回Task ,异步委托也会返回。 Task.Factory.StartNew还返回一个Task ,其结果是delegate参数的结果。 因此,当它们一起使用时,它返回一个Task>>

这个Task>所做的就是执行委托,直到有一个要返回的任务,即到达第一个await时。 如果您只等待该任务完成,则不等待整个方法,只是等待第一个等待之前的部分。

你可以通过使用Task.Unwrap来修复它,它创建一个表示Task>>Task

 Task wrapperTask = Task.Factory.StartNew(...); Task actualTask = wrapperTask.Unwrap(); Task.WaitAll(actualTask); 

您的代码存在的问题是有两个任务在起作用。 一个是Task.Factory.StartNew调用的结果,它导致在线程池上执行匿名函数。 但是,您的匿名函数将被编译为生成嵌套任务,表示其异步操作的完成。 当你等待Task> ,你只是在等待外部任务。 要等待内部任务,您应该使用Task.Run而不是Task.Factory.StartNew ,因为它会自动解开您的内部任务:

 Random rng = new Random((int)DateTime.UtcNow.Ticks); int delay = rng.Next(1500, 15000); Task testTask = Task.Run( async () => { DateTime startTime = DateTime.Now; Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), delay); await Task.Delay(delay); Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); return delay; }); Task[] tasks = new[] { testTask }; Task.WaitAll(tasks); Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); // make console stay open till user presses enter Console.ReadLine(); 

在这里, Task.WaitAll等待外部任务而不是内部任务。 使用Task.Run没有嵌套任务。 这是最佳实践解决方案。 另一种解决方案是等待内部任务。 例如:

 Task testTask = Task.Factory.StartNew( async (obj) => { ... } ).Unwrap(); 

要么:

 testTask.Wait(); testTask.Result.Wait(); 

经过多次拧紧和拔毛后,我终于决定摆脱异步lambda,只使用System.Threading.Thread.Sleep方法,看看是否有任何区别。

新代码最终结果如下:

 Random rng = new Random((int)DateTime.UtcNow.Ticks); int delay = rng.Next(1500, 15000); Task testTask = Task.Factory.StartNew( (obj) => { DateTime startTime = DateTime.Now; Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj); System.Threading.Thread.Sleep((int)obj); Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); return obj; }, delay ); Task[] tasks = new Task[] { testTask }; Task.WaitAll(tasks); Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); // make console stay open till user presses enter Console.ReadLine(); 

注意:由于从lambda方法中删除了async关键字,因此任务类型现在可以只是Task而不是Task> – 您可以在上面的代码中看到此更改。

瞧,瞧! 有效! 我得到了’等待完毕’。 任务完成后的消息。

唉,我记得在某处你不应该在你的Task代码中使用System.Threading.Thread.Sleep() 。 不记得为什么; 但是因为它只是用于测试而且大多数任务实际上都在做一些花费时间而不是假装做一些需要时间的事情 ,这不应该是一个问题。

希望这可以帮助一些人。 我绝对不是世界上最好的程序员(甚至是关闭),我的代码可能不是很好,但如果它对某人有帮助,那就太好了! 🙂

谢谢阅读。

编辑:我的问题的其他答案解释了为什么我遇到了问题,这个答案只是错误地解决了问题。 更改为Thread.Sleep(x) 无效 。 感谢所有回答并帮助我的人!