带有异步 lambda 和 Task.WaitAll 的 Task.Factory.StartNew

Posted

技术标签:

【中文标题】带有异步 lambda 和 Task.WaitAll 的 Task.Factory.StartNew【英文标题】:Task.Factory.StartNew with async lambda and Task.WaitAll 【发布时间】:2015-08-29 10:53:48 【问题描述】:

我正在尝试在任务列表中使用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,那么您需要在调用堆栈中一直使用异步。 【参考方案1】:

Task.Factory.StartNew 无法识别async 委托,因为没有接受返回Task 的函数的重载。

这加上其他原因(请参阅StartNew is dangerous)是您应该在此处使用Task.Run 的原因:

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

【讨论】:

这工作......有点。现在不确定问题,但我收到一个任务取消异常,试图返回一个字符串。似乎是一个不同的问题,所以我要开始一个新的 SO 线程。谢谢。 太糟糕了 Task.run 没有给你TaskCreationOptions Task.Factory.StartNew(Func&lt;Task&gt; function 似乎在 .NET Standard 1.6 中可用 - 可能必须 Unwrap() 得到 Task 虽然 :)【参考方案2】:

由于异步 lambda,这不会等待。那我该怎么做 在我的 lambda 中等待 I/O 操作?

Task.WaitAll 不等待异步 lambda 提供的 IO 工作完成的原因是,Task.Factory.StartNew 实际上返回了 Task&lt;Task&gt;。由于您的列表是 List&lt;Task&gt;(并且 Task&lt;T&gt; 派生自 Task),因此您等待由 StartNew 启动的外部任务,同时忽略由异步 lambda 创建的内部任务。这就是为什么他们说Task.Factory.StartNew 在异步方面是危险的。

你怎么能解决这个问题?您可以显式调用Task&lt;Task&gt;.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);

【讨论】:

【参考方案3】:

你可以这样做。

    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,因为您很容易死锁。 WaitAll 是 Jacob 根据问题想要做的事情。【参考方案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...
            );
        
    

【讨论】:

以上是关于带有异步 lambda 和 Task.WaitAll 的 Task.Factory.StartNew的主要内容,如果未能解决你的问题,请参考以下文章

在异步 AWS Lambda 函数中使用带有 node-fetch 模块的 node.js 时遇到问题

Task.Run 中的异步 lambda 与常规 lambda [重复]

C# ForEach 循环,带有异步任务和依赖的后异步任务

如何让 Lambda 函数等待异步操作完成?

如何标记异步 lambda 表达式?

异步Lambda表达式问题的探索