可选锁定是否继续受竞争条件影响?

时间:2013-01-26 17:44:09

标签: .net concurrency task-parallel-library

我刚刚开始使用任务并行库。有问题的任务是以尽可能平行的方式处理结果,但保持结果的顺序。

此外,可以随时添加项目,直到设置了标志,表示不再接受任何项目。

此外,一些客户需要在完成所有结果后收到通知(只有在不再接受任何项目时才会通知)。

我已经提出了以下简化的样本,这似乎在我的所有测试中都很好。

class Program
{
    static void Main(string[] args)
    {
        for (int i = 1; i < random.Next(5, 21); ++i)
        {
            AddItem(i);
        }

        finishedAddingItems = true;

        completion.Task.Wait();
        Console.WriteLine("Finished");
        Console.ReadKey();
    }

    static TaskCompletionSource<bool> completion = 
                            new TaskCompletionSource<bool>();

    static bool finishedAddingItems = false;

    static Random random = new Random();

    class QueueResult
    {
        public int data;
        public int IsFinished;
    }

    static ConcurrentQueue<QueueResult> queue = 
                           new ConcurrentQueue<QueueResult>();

    static object orderingLockObject = new object();

    static void AddItem(int i)
    {
        var queueItem = new QueueResult { data = i, IsFinished = 0 };

        queue.Enqueue(queueItem);

        Task.Factory
            .StartNew(() => 
            { 
                for (int busy = 0; 
                     busy <= random.Next(9000000, 90000000); 
                     ++busy) 
                { }; 
                Interlocked.Increment(ref queueItem.IsFinished); 
            })
            .ContinueWith(t =>
            {
                QueueResult result;

                //the if check outside the lock is to avoid tying up resources
                //needlessly, since only one continuation can actually process
                //the queue at a time.
                if (queue.TryPeek(out result) 
                    && result.IsFinished == 1)
                {
                    lock (orderingLockObject)
                    {
                        while (queue.TryPeek(out result) 
                               && result.IsFinished == 1)
                        {
                            Console.WriteLine(result.data);
                            queue.TryDequeue(out result);
                        }

                        if (finishedAddingItems && queue.Count == 0)
                        {
                            completion.SetResult(true);
                        }
                    }
                }
            });
    }
}

但是,我无法说服自己是否存在可能无法处理项目的潜在竞争条件?

1 个答案:

答案 0 :(得分:2)

我认为您的代码可能无法正常运行,因为您没有将IsFinished声明为volatile,而是直接在锁定之外访问它。在任何情况下,正确使用double-checked locking都很难,所以除非你真的需要,否则你不应该这样做。

此外,您的代码也非常混乱(在一个类中使用int而不是bool,不必要的ContinueWith(),...)并且至少包含一个以上的线程 - 安全问题(Random不是线程安全的。)

由于这一切,我建议您了解TPL的更高级部分。在您的情况下,PLINQ听起来像是正确的解决方案:

var source = Enumerable.Range(1, random.Next(5, 21)); // or some other collection

var results = source.AsParallel()
                    .AsOrdered()
                    .WithMergeOptions(ParallelMergeOptions.NotBuffered)
                    .Select(i => /* perform your work here */);

foreach (int i in results)
    Console.WriteLine(i);