从Parallel.ForEach和Task.Factory.StartNew中记录每个项目的异常

时间:2012-08-15 17:57:15

标签: c# task-parallel-library

我正在尝试在列表中使用Parallel.ForEach,并且对于列表中的每个项目,尝试进行数据库调用。我试图记录每个项目是否有错误。只是想与专家在这里检查如果我正在做正确的方式。对于此示例,我使用文件访问而不是数据库访问来模拟I / O.

    static ConcurrentQueue<IdAndErrorMessage> queue = new ConcurrentQueue<IdAndErrorMessage>();
    private static void RunParallelForEach()
    {
      List<int> list = Enumerable.Range(1, 5).ToList<int>();
      Console.WriteLine("Start....");
      Stopwatch stopWatch = new Stopwatch();
      stopWatch.Start();
      Parallel.ForEach(list, (tempId) =>
      {
        string errorMessage = string.Empty;
        try
        {
          ComputeBoundOperationTest(tempId);
           try
           {
              Task[] task = new Task[1]
              {
               Task.Factory.StartNew(() =>  this.contentFactory.ContentFileUpdate(content, fileId))
              };
           }
           catch (Exception ex)
           {
              this.tableContentFileConversionInfoQueue.Enqueue(new ContentFileConversionInfo(fileId, ex.ToString()));
           }
        }
        catch (Exception ex)
        {
          errorMessage = ex.ToString();
        }
        if (queue.SingleOrDefault((IdAndErrorMessageObj) => IdAndErrorMessageObj.Id == tempId) == null)
        {
            queue.Enqueue(new IdAndErrorMessage(tempId, errorMessage));
        }
     }
     );
     Console.WriteLine("Stop....");
     Console.WriteLine("Total milliseconds :- " + stopWatch.ElapsedMilliseconds.ToString());
}

以下是辅助方法: -

private static byte[] FileAccess(int id)
{
    if (id == 5)
    {
      throw new ApplicationException("This is some file access exception");
    }
     return File.ReadAllBytes(Directory.GetFiles(Environment.SystemDirectory).First());
            //return File.ReadAllBytes("Files/" + fileName + ".docx");
}

 private static void ComputeBoundOperationTest(int tempId)
 {
    //Console.WriteLine("Compute-bound operation started for :- " + tempId.ToString());
    if (tempId == 4)
    {
       throw new ApplicationException("Error thrown for id = 4 from compute-bound operation");
    }
    Thread.Sleep(20);
 }

 private static void EnumerateQueue(ConcurrentQueue<IdAndErrorMessage> queue)
 {
    Console.WriteLine("Enumerating the queue items :- ");
    foreach (var item in queue)
    {
      Console.WriteLine(item.Id.ToString() + (!string.IsNullOrWhiteSpace(item.ErrorMessage) ? item.ErrorMessage : "No error"));
    }
 }

3 个答案:

答案 0 :(得分:2)

没有理由这样做:

/*Below task is I/O bound - so do this Async.*/
Task[] task = new Task[1]
{
    Task.Factory.StartNew(() => FileAccess(tempId))
};
Task.WaitAll(task);

通过在一个单独的任务中安排它,然后立即等待它,你只是占用更多的线程。你最好离开这个:

/*Below task is I/O bound - but just call it.*/
FileAccess(tempId);

话虽如此,鉴于您为每个项目创建了一个记录值(异常或成功),您可能需要考虑将其写入方法,然后将整个事件调用为PLINQ查询。

例如,如果将其写入处理try / catch(没有线程)的方法,并返回“已记录的字符串”,即:

string ProcessItem(int id) { // ...

您可以将整个操作编写为:

var results = theIDs.AsParallel().Select(id => ProcessItem(id));

答案 1 :(得分:1)

您可能希望从线程代码中删除Console.WriteLine。原因是每个Windows应用程序只能有一个控制台。因此,如果两个或多个线程要与控制台并行写入,则必须等待。

替换为您的自定义错误队列,您可能希望查看.NET 4's Aggregate Exception并捕获它并相应地处理异常。 InnerExceptions属性将为您提供必要的例外列表。更多here

一般的代码审查评论,不要在4中使用像if (tempId == 4)这样的幻数,而是定义一些const来表示4代表什么。例如if (tempId == Error.FileMissing)

答案 2 :(得分:0)

Parallel.ForEach同时运行一个action / func,直到一定数量的同时实例。如果每个迭代所做的事情本身并不是彼此独立的,那么你就不会获得任何性能提升。并且,可能通过引入昂贵的上下文切换和争用来降低性能。你说你想做一个“数据库调用”并用文件操作模拟它。如果每次迭代使用相同的资源(例如,数据库表中的同一行;或者尝试写入同一位置的同一文件),那么它们实际上不会并行运行。只有一个将一次运行,其他人只是“等待”获取资源 - 不必要地使你的代码变得复杂。

您还没有详细说明每次迭代要做什么;但是当我和其他程序员遇到这样的情况时,他们几乎总是没有真正并行处理,他们只是简单地将foreach替换为Parallel.ForEach,希望能够神奇地获得性能或神奇地使用多CPU /核心处理器。