为啥我在使用任务时无法正确填充集合? [复制]
Posted
技术标签:
【中文标题】为啥我在使用任务时无法正确填充集合? [复制]【英文标题】:Why collection doesn't fill properly when I am using tasks? [duplicate]为什么我在使用任务时无法正确填充集合? [复制] 【发布时间】:2021-06-02 20:06:47 【问题描述】:我有一段非常简单的代码:
public class CustomTester
public async Task RunTheTestAsync()
IList<int> someCollectionToFill = new List<int>();
var someCollectionToIterate = Enumerable.Range(0, 20);
IEnumerable<Task> tasks = someCollectionToIterate.Select(
valueFromCollection => Task.Run(async () =>
try
await Task.Delay(1);
someCollectionToFill.Add(valueFromCollection);
catch (Exception ex)
Console.WriteLine(ex.Message);
)).ToList();
await Task.WhenAll(tasks);
PrintCollectionValues(someCollectionToFill);
private void PrintCollectionValues(IList<int> collection)
Console.WriteLine("Total count: " + collection.Count);
我是这样执行的:
await new CustomTester().RunTheTestAsync();
当代码执行时,我有以下输出:
Total count: 13
或:
Total count: 14
等等……
但我期待:
Total count: 20
为什么会这样?
【问题讨论】:
您希望非线程安全集合在线程场景中安全工作的任何特殊原因? 这大约是您研究线程安全的时候,是的,锁在这里会有所帮助。但是,除非您了解它为什么会有所帮助,否则您是在盲目地飞行 您必须自己使用锁来创建“线程安全列表”,但幸运的是已经有一些并发集合。您可以将 ConcurrentBag 用于“someCollectionToFill”,一切都会好起来的。 一个列表基于一个非线程安全的数组,而这又只是一堆内存。每次添加到列表时,它都有可能调整基本数组的大小。由于您实际上无法调整数组的大小,因此运行时实际上需要创建一个更大的新数组,并复制当前内容。如果有 2 个线程同时尝试插入和复制,您可以想象事情会变得有趣。就像多人试图把东西放在一个桶里,桶装满了,然后你必须拿一个更大的桶把它倒进去。当一个人放东西,另一个人倾倒东西时会发生什么 @Phil 如果您在多个线程中使用非线程安全的东西,这并不意味着会自动引发异常。也有可能该对象未处于您所观察到的预期状态。 【参考方案1】:你正在使用一个列表,一个非线程安全的集合,同时来自多个线程。这是不安全的。要么使用
lock(someCollectionToFill)
someCollectionToFill.Add(valueFromCollection);
或
var someCollectionToFill = new ConcurrentBag<int>();
我强烈建议在尝试使用任何多线程之前阅读线程安全。 Multi threading best practices 是一个好的开始,但它并没有涵盖所有内容,所以我建议您在谷歌上搜索一下。编写多线程代码很困难,而且很容易出错,导致断断续续的 bug,很难调试和理解。
【讨论】:
谢谢。我一定会熟悉这个话题。以上是关于为啥我在使用任务时无法正确填充集合? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
为啥我不能在编译时将整数添加到泛型集合中,即使使用引用类型作为数字创建的泛型? [复制]