的任务.WaitAll不等待任务完成
本文关键字:任务 等待 WaitAll | 更新日期: 2023-09-27 18:09:09
在试图找出新的(也许现在不是那么新,但对我来说,无论如何)c#中的Task
异步编程时,我遇到了一个问题,花了我一点时间来弄清楚,我不知道为什么。
我已经解决了这个问题,但我仍然不确定为什么它是一个问题开始。我只是想分享一下我的经验,以防有人遇到同样的情况。
如果有任何大师愿意告诉我问题的原因,那将是非常好的,我非常感激。我总是喜欢知道为什么有些东西不起作用!
我做了一个测试任务,如下:
Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<Task<object>> testTask = Task.Factory.StartNew<Task<object>>(
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<object>>[] tasks = new Task<Task<object>>[] { 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 -等完了。
06:18.5743 -测试任务在3063.235ms后完成。
可以看到,Task.WaitAll(tasks);
语句没有做太多事情。在继续执行之前,它总共等待了1毫秒。
我已经在下面回答了我自己的"问题"——但正如我上面所说的——如果有人比我更有知识,愿意解释为什么这不起作用,请做!(我认为它可能与方法的执行"步出"有关,一旦它到达await
操作符-然后在等待完成后退后…但我可能错了)
您应该避免使用Task.Factory.StartNew
和async-await。你应该用Task.Run
代替。
一个异步方法返回一个Task<T>
,一个异步委托也这样做。Task.Factory.StartNew
也返回一个Task<T>
,其结果是委托参数的结果。因此,当一起使用时,它返回一个Task<Task<T>>>
。
这个Task<Task<T>>
所做的就是执行委托,直到有一个任务要返回,这是第一个等待到达的时候。如果你只等待那个任务完成,你就不是在等待整个方法,而只是等待第一个await之前的部分。
你可以通过使用Task.Unwrap
来解决这个问题,它会创建一个代表Task<Task<T>>>
的Task<T>
:
Task<Task> wrapperTask = Task.Factory.StartNew(...);
Task actualTask = wrapperTask.Unwrap();
Task.WaitAll(actualTask);
代码的问题是有两个任务在起作用。一个是Task.Factory.StartNew
调用的结果,它导致在线程池上执行匿名函数。然而,匿名函数又被编译为生成一个嵌套的任务,表示其异步操作的完成。当您等待Task<Task<object>>
时,您只等待外部任务。要等待内部任务,应该使用Task.Run
而不是Task.Factory.StartNew
,因为它会自动展开内部任务:
Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<int> 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<int>[] 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<object> testTask = Task.Factory.StartNew(
async (obj) =>
{
...
}
).Unwrap();
或:
testTask.Wait();
testTask.Result.Wait();
经过一番折腾,我终于决定摆脱async lambda,只使用System.Threading.Thread.Sleep
方法,看看是否有什么不同。
新代码是这样结束的:
Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<object> testTask = Task.Factory.StartNew<object>(
(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<object>[] tasks = new Task<object>[] { 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<object>
而不是Task<Task<object>>
-您可以在上面的代码中看到这个变化。
,瞧!它工作!我听到的是"等完了"。任务完成后的消息。
唉,我记得在某处读到你不应该在你的Task
代码中使用System.Threading.Thread.Sleep()
。不记得为什么了;但是,由于它只是用于测试,并且大多数任务实际上是做一些需要时间的事情,而不是假装做一些需要时间的事情,这应该不是一个真正的问题。
希望这能帮助一些人。我绝对不是世界上最好的程序员(甚至接近),我的代码可能不是很好,但如果它能帮助别人,那就太好了!:)
感谢您的阅读。
编辑:我的问题的其他答案解释了为什么我有这个问题,而这个答案只是错误地解决了这个问题。将更改为Thread.Sleep(x) 没有效果。感谢所有回答和帮助我的人!