Task.Factory.StartNew与异步lambda和Task.WaitAll

37
我试图在任务列表上使用Task.WaitAll。问题是这些任务都是一个异步lambda表达式,它会导致 Tasks.WaitAll 永远不会等待它们。
下面是示例代码块:
List<Task> tasks = new List<Task>();
tasks.Add(Task.Factory.StartNew(async () =>
{
    using (dbContext = new DatabaseContext())
    {
        var records = await dbContext.Where(r => r.Id = 100).ToListAsync();
        //do long cpu process here...
    }
}
Task.WaitAll(tasks);
//do more stuff here  

由于异步 Lambda,这并不会等待。那么我该如何在我的 Lambda 中等待 I/O 操作呢?


如果你在另一个线程上启动任务后第一件事就是在Task.WaitAll调用上阻塞,那么这样做的意义何在呢?通过摆脱ToListAsync并将其改成同步运行的ToList,您可以获得更好的性能。(或者如果您确实想使用ToListAsync,那么您需要在整个调用堆栈中都使用异步)。 - Scott Chamberlain
4个回答

48

Task.Factory.StartNew不认可返回Task的异步委托,因为它没有重载接受这样的函数。

加上其他原因(请查看StartNew is dangerous),这就是为什么你应该在这里使用Task.Run的原因:

tasks.Add(Task.Run(async () => ...

这个有点奏效...但现在不确定问题所在,我尝试返回一个字符串时出现了任务取消异常。似乎是另一个问题,所以我要开始一个新的SO线程。谢谢。 - Jacob Roberts
12
很遗憾,Task.run 方法无法提供 TaskCreationOptions 参数。 - Douglas Gaskell
1
Task.Factory.StartNew(Func<Task> function) 似乎可用于 .NET Standard 1.6 - 不过可能需要 Unwrap() 结果的 Task :) - urbanhusky

36

由于异步 Lambda 的原因,Task.WaitAll 不会等待 I/O 操作完成。那么我该如何在 Lambda 中等待 I/O 操作呢?

Task.WaitAll 不等待异步 Lambda 提交的 I/O 工作完成,是因为 Task.Factory.StartNew 实际上返回一个 Task<Task>。由于您的列表是 List<Task>(而 Task<T> 派生自 Task),所以您等待由 StartNew 开始的外部任务,同时忽略了异步 Lambda 创建的内部任务。这就是为什么他们说 Task.Factory.StartNew 在异步方面是有风险的

您可以通过显式调用 Task<Task>.Unwrap() 来获取内部任务以解决此问题:

List<Task> tasks = new List<Task>();
tasks.Add(Task.Factory.StartNew(async () =>
{
    using (dbContext = new DatabaseContext())
    {
        var records = await dbContext.Where(r => r.Id = 100).ToListAsync();
        //do long cpu process here...
    }
}).Unwrap());

或者像其他人说的那样,你可以调用Task.Run

tasks.Add(Task.Run(async () => /* lambda */);

另外,如果您想要正确执行事情,您将需要使用Task.WhenAll,因为它是可以异步等待的,而不是Task.WaitAll,后者会同步阻塞:

await Task.WhenAll(tasks);

-2
你可以这样做。
    void Something()
    {
        List<Task> tasks = new List<Task>();
        tasks.Add(ReadAsync());
        Task.WaitAll(tasks.ToArray());
    }

    async Task ReadAsync() {
        using (dbContext = new DatabaseContext())
        {
            var records = await dbContext.Where(r => r.Id = 100).ToListAsync();
            //do long cpu process here...
        }
    }

这几乎是一个好答案。如果你正在使用async,你不应该在Task.WaitAll上阻塞,因为你很容易死锁。 - Scott Chamberlain
WaitAll是根据问题Jacob想要做的。 - hebinda

-4

你必须使用Task.ContinueWith方法。就像这样

List<Task> tasks = new List<Task>();
tasks.Add(Task.Factory.StartNew(() =>
{
    using (dbContext = new DatabaseContext())
    {
        return dbContext.Where(r => r.Id = 100).ToListAsync().ContinueWith(t =>
            {
                var records = t.Result;
                // do long cpu process here...
            });
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接