Task.WaitAll 没有等待任务完成

Task.WaitAll not waiting for task to complete

在尝试找出新的(现在可能不是那么新,但对我来说还是新的)Task C# 中的异步编程时,我 运行 遇到了一个问题,这让我有点费力弄清楚,我不确定为什么。

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

如果有任何专家愿意告诉我问题的原因,那将是非常好的,我们将不胜感激。我总是想知道 为什么 有些东西不起作用!

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

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 - Starting test task with delay of 3053ms.
6:06:15.5662 - Finished waiting.
6:06:18.5743 - Test task finished after 3063.235ms.

如您所见,Task.WaitAll(tasks); 语句没有做太多事情。它在继续执行之前等待了 g运行d 总共 1 毫秒。

我已经在下面回答了我自己的 "question" - 但正如我上面所说 - 如果有比我更了解的人愿意解释为什么这不起作用,请做! (我 认为 它可能与方法的执行 'stepping-out' 有关,一旦它到达 await 运算符 - 然后在等待完成后退回...但我可能错了)

经过多次折腾和纠缠,我终于决定摆脱异步 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>> - 您可以在代码中看到此更改以上。

而且,瞧!有效!我在任务完成后收到 'Finished waiting.' 消息。

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

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

感谢阅读。

Edit: The other answers to my question explain why I had the problem I did and this answer only solved the problem by mistake. Changing to Thread.Sleep(x) had no effect. Thank you to all the people who answered and helped me out!

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

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

或者:

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

您应该避免将 Task.Factory.StartNew 与异步等待一起使用。您应该改用 Task.Run

异步方法return是Task<T>,异步委托也是如此。 Task.Factory.StartNew 也 return 是一个 Task<T>,其结果是委托参数的结果。所以一起使用时 return 是 Task<Task<T>>>.

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

您可以通过使用 Task.Unwrap 来解决这个问题,它会创建一个 Task<T> 来表示 Task<Task<T>>>:

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

您的代码的问题是有两个任务在起作用。一个是你的 Task.Factory.StartNew 调用的结果,它导致匿名函数在线程池上执行。但是,您的匿名函数会依次编译生成一个 nested 任务,代表其异步操作的完成。当您等待 Task<Task<object>> 时,您只是在等待 outer 任务。要等待内部任务,您应该使用 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();