的任务.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操作符-然后在等待完成后退后…但我可能错了)

的任务.WaitAll不等待任务完成

您应该避免使用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) 没有效果。感谢所有回答和帮助我的人!