【问题标题】:Thread safe when using Task.WhenAll [closed]使用 Task.WhenAll 时线程安全 [关闭]
【发布时间】:2020-01-23 10:41:09
【问题描述】:
public async Task<Informations> DoSthAsync
{
   var informations = new List<Informations>();
   await Task.WhenAll(FirstTask(informations), SecondTask(informations));

   return informations;
}

public async Task FirstTask(List<Informations> list)
{
   await Task.Run( () => //do sth with list);
}

public async Task SecondTask(List<Informations> list)
{ 
  await Task.Run( () => //do sth with list);
}

我想问一下,如果这段代码可能会导致问题,两个任务何时会使用同一个列表?

谢谢。

【问题讨论】:

  • 如果任务开始后没有任何更改列表,则该列表是安全的。虽然从长远来看,您可能应该使用ImmutableList&lt;&gt;
  • 这取决于用列表做某事
  • 第一个和第二个任务如何使用List?是否会被修改,例如添加、替换或删除元素?
  • 还有List 不是唯一的问题...。即使您只迭代列表但更改Informations item,代码将是“线程不安全”并且ConcurrentBag 将无济于事......所以对于更改集合 ConcurrentBag 是一种方式,但是对于修改项目,您需要在项目上 lock

标签: c# multithreading thread-safety


【解决方案1】:

如果“do sth with list”表示写入列表,那么这不是线程安全的,因为List&lt;T&gt; 不是线程安全的集合。来自the docs

这种类型的公共静态(在 Visual Basic 中为共享)成员是线程安全的。不保证任何实例成员都是线程安全的。

List&lt;T&gt; 上执行多个读取操作是安全的,但如果在读取集合时对其进行了修改,则可能会出现问题。为确保线程安全,请在读取或写入操作期间锁定集合。要使一个集合能够被多个线程访问以进行读写,您必须实现自己的同步。对于具有内置同步的集合,请参阅 System.Collections.Concurrent 命名空间中的类。有关固有的线程安全替代方案,请参阅 ImmutableList&lt;T&gt; 类。

如果您的任务只是从列表中读取,并且没有添加任何内容,那么您很好。

如果您的任务需要写入列表,那么最佳解决方案取决于您具体在做什么。如果您正在做大量 CPU 密集型工作,然后将结果添加到列表中,请考虑从 FirstTaskSecondTask 返回结果,然后从 DoSthAsync 内部将它们添加到列表中:

public async Task<List<Informations>> DoSthAsync()
{
    // Kick off both tasks
    var firstTask = FirstTask();
    var secondTask = SecondTask();
    await Task.WhenAll(firstTask, secondTask);
    var informations = firstTask.Result;
    informations.AddRange(secondTask.Result);
    return informations;
}

public Task<List<Informations>> FirstTask()
{
    return Task.Run(() => ...);
}

public Task<List<Informations>> SecondTask()
{
    return Task.Run(() => ...);
}

或者,您可以使用concurrent collections 之一来收集您的结果。如果结果的顺序不重要,请使用ConcurrentBag&lt;T&gt;

public async Task<IReadOnlyCollection<Informations>> DoSthAsync
{
    var informations = new ConcurrentBag<Informations>();
    await Task.WhenAll(FirstTask(informations), SecondTask(informations));

    return informations;
}

public async Task FirstTask(ConcurrentBag<Informations> list)
{
    await Task.Run( () => //do sth with list);
}

public async Task SecondTask(ConcurrentBag<Informations> list)
{ 
    await Task.Run( () => //do sth with list);
}

【讨论】:

    【解决方案2】:

    List&lt;T&gt; 不是线程安全的。建议使用一个集合,或者添加一个lock,这将防止多个线程同时访问该块。允许您安全地改变列表对象,同时避免race conditions

    lock(obj)
    {
    // informations mutations
    }
    

    在这种情况下,您可以使用ConcurrentBag&lt;T&gt; 而不是List&lt;T&gt;This collection 确实支持安全的并发访问和修改

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-20
      • 2021-08-12
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      • 1970-01-01
      • 2021-09-01
      • 2015-09-10
      相关资源
      最近更新 更多