c# - 在不同的、独立的对象上工作的 C# 任务,仍然会出现同步错误,为什么?
问题描述
我的程序n
每次都在一组任务中运行任务。每个任务将数据写入Queue<string>
他自己的对象,由队列Queue<string>
中的一个索引提供。List<Queue<string>>
这些任务不共享数据或队列,但我仍然遇到同步错误。我知道数据结构不是线程安全的,我不明白为什么它们应该是,为什么我会得到错误,因为每个Task
人都有自己的数据结构,什么可能导致错误?
这是一个简单的代码来演示:
class Program
{
static int j = 0;
List<Queue<string>> queueList = new List<Queue<string>>();
public void StartTasts(int n)
{
for (int i = 0; i < n; i++)
queueList.Add(new Queue<string>());
List<Task> tsk = new List<Task>();
for (int TaskGroup = 0; TaskGroup < 10; TaskGroup++)
{ //10 groups of task
//each group has 'n' tasks working in parallel
for (int i = 0; i < n; i++)
{
//each task gets its own and independent queue from the list
tsk.Add(Task.Factory.StartNew(() =>
{
DoWork(j % n);
}));
j++;
}
//waiting for each task group to finish
foreach (Task t in tsk)
t.Wait();
//after they all finished working with the queues, clear queues
//making them ready for the nest task group
foreach (Queue<string> q in queueList)
q.Clear();
}
}
public void DoWork(int queue)
{
//demonstration of generating strings
//and put them in the correct queue
for (int k = 0; k < 10000; k++)
queueList[queue].Enqueue(k + "");
}
static void Main(string[] args)
{
new Program().StartTasts(10);
}
}
该程序会产生一些错误,例如:
System.ArgumentException: '目标数组不够长。检查 destIndex 和长度,以及数组的下限。
System.IndexOutOfRangeException: '索引超出了数组的范围。' (在队列中)
System.AggregateException:发生一个或多个错误。---> System.ArgumentException:源数组不够长。检查 srcIndex 和长度,以及数组的下限。
和更多错误,不会出现在串行案例中。我很想理解为什么,因为我看不到这些任务如何弄乱彼此的独立队列。
解决方案
问题是正常的变量闭包问题。因为所有任务共享变量 j 的同一个实例,它们都将共享相同的值,最有可能发生的事情是你的循环超级快地启动了 10 个任务,但是在它们中的任何一个可以达到j % n
j 的值之前已经变成了 10 .
制作在 for 循环范围内声明的 k 的本地副本,它应该可以解决您的问题。
public void StartTasts(int n)
{
for (int i = 0; i < n; i++)
queueList.Add(new Queue<string>());
List<Task> tsk = new List<Task>();
for (int TaskGroup = 0; TaskGroup < 10; TaskGroup++)
{ //10 groups of task
//each group has 'n' tasks working in parallel
for (int i = 0; i < n; i++)
{
int k = j; // `int k = i;` would work here too and give you the same results.
tsk.Add(Task.Factory.StartNew(() =>
{
DoWork(k % n);
}));
j++;
}
//waiting for each task group to finish
foreach (Task t in tsk)
t.Wait();
//after they all finished working with the queues, clear queues
//making them ready for the nest task group
foreach (Queue<string> q in queueList)
q.Clear();
}
}
如果您想通过更简单的重新创建来查看问题的实际效果,请尝试使用这个简单的代码。
public static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
int j = i;
Task.TaskFactory.StartNew(() =>
{
Thread.Sleep(10); //Give a little time for the for loop to complete.
Console.WriteLine("i: " + i + " j: " + j);
}
});
Console.ReadLine();
}
推荐阅读
- wordpress - 让子 CSS 文件在 DIVI Theme Wordpress 中最后加载
- bash - 如何在 Ubuntu 上使用 bash 创建 GitHub 存储库机密/加密它?
- python - 如何根据 URL id 使用 Django 创建 URL-Shortener?
- excel - 专注于使用 VBA 的“将 PDF 另存为”窗口
- flutter - 如何在颤动的for循环中动态创建滚动控制器
- android - 为什么 ViewPager 无限循环不起作用?
- string - 检查变量是否以冒号 `:` 开头
- metal - 金属深度测试不适用于多个绘制调用
- javascript - 如何遍历位于后端的整个数据
- dart - 是否可以创建实现或覆盖继承方法的 Dart 类扩展?