我正在尝试在列表中使用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"));
}
}
答案 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 /核心处理器。