首页 > 解决方案 > 在不同的、独立的对象上工作的 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 和长度,以及数组的下限。

和更多错误,不会出现在串行案例中。我很想理解为什么,因为我看不到这些任务如何弄乱彼此的独立队列。

标签: c#parallel-processingmultitasking

解决方案


问题是正常的变量闭包问题。因为所有任务共享变量 j 的同一个实例,它们都将共享相同的值,最有可能发生的事情是你的循环超级快地启动了 10 个任务,但是在它们中的任何一个可以达到j % nj 的值之前已经变成了 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();
}

推荐阅读