在不同的独立对象上工作的C#任务仍然出现同步错误,为什么?

时间:2018-11-25 00:34:16

标签: c# parallel-processing multitasking

我的程序每次一次运行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和length,以及数组的下限。'

     

System.IndexOutOfRangeException:'索引超出数组的范围。' (在队列中)

     

System.AggregateException:发生一个或多个错误。 ---> System.ArgumentException:源数组不够长。检查srcIndex和length,以及数组的下界。

和更多错误,不会出现在序列号案件中。 我很想知道为什么,因为我看不到这些任务如何使彼此的独立队列混乱。

3 个答案:

答案 0 :(得分:4)

问题是正常的变量关闭问题。因为所有任务共享变量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();
}

答案 1 :(得分:1)

您已在任务内部计算出taskId,并在任务外部更改了计算基础。 我仅略微更改了逻辑。我没有任何错误。

namespace Project1
{
    using System.Collections.Generic;
    using System.Threading.Tasks;

    internal class Program
    {
        private static int j = 0;
        private readonly List<Queue<string>> queueList = new List<Queue<string>>();

        public void StartTasts(int n)
        {
            for (var i = 0; i < n; i++)
            {
                this.queueList.Add(new Queue<string>());
            }

            var taskList = new List<Task>();
            for (var taskGroup = 0; taskGroup < 10; taskGroup++)
            {
                // 10 groups of task
                // each group has 'n' tasks working in parallel
                for (var i = 0; i < n; i++)
                {
                    // each task gets its own and independent queue from the list
                    var taskId = j % n;
                    taskList.Add(
                        Task.Factory.StartNew(
                            () =>
                            {
                                this.DoWork(taskId);
                            }));
                    j++;
                }

                // waiting for each task group to finish
                foreach (var t in taskList)
                {
                    t.Wait();
                }

                // after they all finished working with the queues, clear queues
                // making them ready for the nest task group
                foreach (var q in this.queueList)
                {
                    q.Clear();
                }
            }
        }

        public void DoWork(int queue)
        {
            // demonstration of generating strings 
            // and put them in the correct queue
            for (var k = 0; k < 10000; k++)
            {
                this.queueList[queue].Enqueue(k + string.Empty);
            }
        }

        private static void Main(string[] args)
        {
            new Program().StartTasts(10);
        }      
    }
}

答案 2 :(得分:0)

我认为您的问题不是问题所在,似乎列表中的问题本身可能就是问题。

作为使用并行或同步进程的规则,列表不是保存DS的线程。

尝试使用线程保存DS像ConcurrentBag Class