具有容错功能的并行生产者/消费者?

时间:2011-04-14 12:06:31

标签: c# multithreading task task-parallel-library

我需要使用SqlBulkCopy将大型csv文件分块到几个不同的数据库插入中。我打算通过2个单独的任务执行此操作,1用于批量处理CSV文件,另一个用于插入数据库。举个例子就是我的意思:

public class UberTask
{
    private readonly BlockingCollection<Tuple<string,int>> _store = new BlockingCollection<Tuple<string, int>>();

    public void PerformTask()
    {
        var notifier = new UINotifier();
        Task.Factory.StartNew(() =>
                                  {
                                      for (int i =0; i < 10; i++)
                                      {
                                          string description = string.Format("Scenario {0}", i);

                                          notifier.PerformOnTheUIThread(() => Console.WriteLine(string.Format("Reading '{0}' from file", description)));

                                          // represents reading the CSV file.
                                          Thread.Sleep(500);
                                          notifier.PerformOnTheUIThread(() => Console.WriteLine(string.Format("Enqueuing '{0}'", description)));
                                          _store.Add(new Tuple<string, int>(description, i));
                                      }
                                      _store.CompleteAdding();
                                  });

        var consumer = Task.Factory.StartNew(() =>
                                                 {
                                                     foreach (var item in _store.GetConsumingEnumerable())
                                                     {
                                                         var poppedItem = item;
                                                         notifier.PerformOnTheUIThread(() => Console.WriteLine(string.Format("Sending '{0}' to the database", poppedItem.Item1)));
                                                         // represents sending stuff to the database.
                                                         Thread.Sleep(1000);
                                                     }
                                                 });
        consumer.Wait();
        Console.WriteLine("complete");
    }
}

这是配对2套相关任务的好方法吗?上面的代码没有处理(它需要):

  • 如果代表CSV读取的任务出现故障,则另一个任务需要停止(即使_store中仍有项目。)
  • 如果代表db的Task插入错误,则另一个进程可以停止处理。
  • 如果配对任务中的任何一个出现故障,我将需要执行一些操作来回滚数据库更新(我不担心我将如何回滚),这更像是一个如何编码“故障发生在哪里”的问题其中一个配对任务,所以我需要做一些整理“。

对此有任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:2)

您可以使用异常处理和取消令牌来执行此操作。当管道阶段检测到错误时,它会捕获它并设置令牌。这将取消其他阶段。 finally块确保完成对CompleteAdding()的调用。这很重要,因为接收管道阶段可能会在收集等待时被阻止,并且在取消阻止之前不会处理取消。

您还希望在集合中删除任何未处理的对象,或者在管道阶段完成(在最后)和/或整个管道关闭时清理数据库连接。

以下是执行此操作的管道阶段的示例:

    static void LoadPipelinedImages(IEnumerable<string> fileNames, 
                                    string sourceDir, 
                                    BlockingCollection<ImageInfo> original,
                                    CancellationTokenSource cts)
    {
        // ...
        var token = cts.Token;
        ImageInfo info = null;
        try
        {
            foreach (var fileName in fileNames)
            {
                if (token.IsCancellationRequested)
                    break;
                info = LoadImage(fileName, ...);
                original.Add(info, token);
                info = null;
            }                
        }
        catch (Exception e)
        {
            // in case of exception, signal shutdown to other pipeline tasks
            cts.Cancel();
            if (!(e is OperationCanceledException))
                throw;
        }
        finally
        {
            original.CompleteAdding();
            if (info != null) info.Dispose();
        }
    }

整个管道代码如下所示。它还支持通过设置取消令牌从外部(从UI)取消管道。

    static void RunPipelined(IEnumerable<string> fileNames, 
                             string sourceDir, 
                             int queueLength, 
                             Action<ImageInfo> displayFn,
                             CancellationTokenSource cts)
    {
        // Data pipes 
        var originalImages = new BlockingCollection<ImageInfo>(queueLength);
        var thumbnailImages = new BlockingCollection<ImageInfo>(queueLength);
        var filteredImages = new BlockingCollection<ImageInfo>(queueLength);
        try
        {
            var f = new TaskFactory(TaskCreationOptions.LongRunning,
                                    TaskContinuationOptions.None);
            // ...

            // Start pipelined tasks
            var loadTask = f.StartNew(() =>
                  LoadPipelinedImages(fileNames, sourceDir, 
                                      originalImages, cts));

            var scaleTask = f.StartNew(() =>
                  ScalePipelinedImages(originalImages, 
                                       thumbnailImages, cts));

            var filterTask = f.StartNew(() =>
                  FilterPipelinedImages(thumbnailImages, 
                                        filteredImages, cts));

            var displayTask = f.StartNew(() =>
                  DisplayPipelinedImages(filteredImages.GetConsumingEnumerable(), 
                       ... cts));

            Task.WaitAll(loadTask, scaleTask, filterTask, displayTask);
        }
        finally
        {
            // in case of exception or cancellation, there might be bitmaps
            // that need to be disposed.
            DisposeImagesInQueue(originalImages);
            DisposeImagesInQueue(thumbnailImages);
            DisposeImagesInQueue(filteredImages);                
        }
    }

有关完整示例,请参阅下载中的Pipeline示例:

http://parallelpatterns.codeplex.com/releases/view/50473

在这里讨论:

http://msdn.microsoft.com/en-us/library/ff963548.aspx