【问题标题】:C# asynchronous tasks with Task.WhenAll() not workingTask.WhenAll() 的 C# 异步任务不起作用
【发布时间】:2019-11-13 09:38:56
【问题描述】:

我正在尝试异步执行多个任务。然后在继续之前等待所有内容。 但不知何故,我的所有任务都没有完成。查看我的示例代码。

//List from database
List<Promotion> promotions = _dbContext.promotions.ToList();

var promotionTasks = new List<Task>();
List<Promotion> outputPromotions = new List<Promotion>();
foreach (var promotion in promotions)
{
    var task = Task.Run(() =>
    {
        var promotionStatus = GetPromotionStatus(promotion).Result;
        var newPromotion = new Promotion
        {
            Id = promotion.Id,
            Name = promotion.Name,
            Code = promotion.Code,
            StatusId = promotionStatus
        };

        outputPromotions.Add(newPromotion);
    });
    promotionTasks.Add(task);
}

//await Task.WhenAll(promotionTasks);
Task.WaitAll(promotionTasks.ToArray());

//Output list
return outputPromotions;

例如 我在promotions 中获得了 50 个值(这是原始列​​表及其来自数据库)。然后在完成所有任务后,我没有一致地获得 outputPromotions 中的所有 50 个值。 outputPromotions 它给了我 49,48,50,46 的计数,而不是每次都是 50。 我试过 await Task.WhenAll(promotionTasks);Task.WaitAll(promotionTasks.ToArray()); 两者但结果相同。

谁能建议我在这里做错了什么?

【问题讨论】:

  • List&lt;T&gt; 对于多线程访问是不安全的。
  • 在循环中使用Task.Run 仅通过调用.Result阻止 是弄巧成拙
  • GetPromotionStatus 是做什么的?这很重要。 Task.Run 可能不需要,代码可以转换为简单的 LINQ 查询和 Task.WhenAll

标签: c# multithreading asynchronous asp.net-core async-await


【解决方案1】:

List&lt;&gt; 不是线程安全的。 Task.Run 似乎也没有做任何有用的事情 - 它只是阻止等待另一个异步方法响应。

async-await 和阻塞调用(如 .Result)的混合也可能导致潜在的死锁

让代码一直异步

参考Async/Await - Best Practices in Asynchronous Programming

代码可以替换为:

var tasks = promotions.Select(async promotion=>
               {
                   var status=await GetPromotionStatus(promotion);
                   return new Promotion                    
                   {
                       Id = promotion.Id,
                       Name = promotion.Name,
                       Code = promotion.Code,
                       StatusId = status
                   }
               });
Promotion[] newPromotions = await Task.WhenAll(tasks);

如果你想将该数组转换为列表,你可以在上面使用.ToList(),或者List(IEnumerable) constructor,例如:

var newList = newPromotions.ToList();

【讨论】:

  • 感谢您的回答将尝试这个。
  • 您评论说 List 显然不是线程安全的,我也知道这一点。但是,查看 Microsoft 的文档 (docs.microsoft.com/en-us/dotnet/api/…),他们的 WhenAll 示例采用了一个任务列表,并且似乎按预期工作。是因为它们正在同步等待WhenAll 结果(即WhenAll(tasks).Wait())吗?我之所以这样问,是因为在我的情况下,使用任务列表导致只有列表中的第一个任务运行完成。所有其他人似乎都以某种方式挂起......
  • @Charlesd'Avernas 代码没有修改列表。 await Task.WhenAll() 同步运行 a 列表填满任务
  • t.Wait() 虽然是一个非常、非常、非常、非常糟糕的例子。之所以使用它只是因为他们无法使用await,因为Main方法是void而不是async Task
  • 好的,有道理。是的,我知道,我发现他们可以在他们的官方文档中放置这样一个糟糕的例子,而他们到处宣传完全异步,这在应用程序的入口点(静态异步任务 Main())中是完全可行的,这令人难以置信。在最坏的情况下,他们应该使用 .GetAwaiter().GetResult() 方法,即使真的很糟糕,也比调用 Wait() 要好得多。
【解决方案2】:

您的任务如下所示:

var task = Task.Run(() =>
{
    var promotionStatus = GetPromotionStatus(promotion).Result;
    var newPromotion = new Promotion
    {
        Id = promotion.Id,
        Name = promotion.Name,
        Code = promotion.Code,
        StatusId = promotionStatus
    };

    outputPromotions.Add(newPromotion);
});

outputPromotions 只是列表。它不是多线程安全的。因此,当您尝试将新项目添加到 outputPromotions 时,您会遇到竞争情况

建议将所有这些促销活动添加到列表中您完成所有任务之后。通常这不会成为您性能的瓶颈。

【讨论】:

  • 另外,为什么使用 .Result ?这个答案将受益于展示更好的方法。
猜你喜欢
  • 2012-05-18
  • 2015-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-24
  • 2017-03-29
  • 2019-09-23
相关资源
最近更新 更多