C# 在Parallel.ForEach和Task.Factory.StartNew中记录每个项的异常
我正在尝试对列表使用Parallel.ForEach,并尝试对列表中的每个项目进行数据库调用。我正在尝试记录每个项目是否有错误。我只是想和这里的专家们确认一下我的做法是否正确。对于本例,我使用文件访问而不是数据库访问来模拟I/OC# 在Parallel.ForEach和Task.Factory.StartNew中记录每个项的异常,c#,task-parallel-library,C#,Task Parallel Library,我正在尝试对列表使用Parallel.ForEach,并尝试对列表中的每个项目进行数据库调用。我正在尝试记录每个项目是否有错误。我只是想和这里的专家们确认一下我的做法是否正确。对于本例,我使用文件访问而不是数据库访问来模拟I/O static ConcurrentQueue<IdAndErrorMessage> queue = new ConcurrentQueue<IdAndErrorMessage>(); private static void Run
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());
}
静态ConcurrentQueue队列=新建ConcurrentQueue();
私有静态void RunParallelForEach()
{
List=Enumerable.Range(1,5).ToList();
Console.WriteLine(“开始…”);
秒表秒表=新秒表();
秒表。开始();
Parallel.ForEach(list,(tempId)=>
{
string errorMessage=string.Empty;
尝试
{
计算边界操作测试(tempId);
尝试
{
任务[]任务=新任务[1]
{
Task.Factory.StartNew(()=>this.contentFactory.ContentFileUpdate(content,fileId))
};
}
捕获(例外情况除外)
{
这个.tableContentFileConversionInfo.Enqueue(新的ContentFileConversionInfo(fileId,例如.ToString());
}
}
捕获(例外情况除外)
{
errorMessage=ex.ToString();
}
if(queue.SingleOrDefault((IdAndErrorMessageObj)=>IdAndErrorMessageObj.Id==tempId)==null)
{
排队(新的IdAndErrorMessage(tempId,errorMessage));
}
}
);
控制台。写入线(“停止…”);
WriteLine(“总毫秒:-”+stopWatch.elapsedmillesons.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"));
}
}
私有静态字节[]文件访问(int-id)
{
如果(id==5)
{
抛出新的ApplicationException(“这是一些文件访问异常”);
}
返回File.ReadAllBytes(Directory.GetFiles(Environment.SystemDirectory.First());
//返回File.ReadAllBytes(“Files/”+fileName+“.docx”);
}
专用静态void ComputeBoundOperationTest(int tempId)
{
//WriteLine(“为:-“+tempId.ToString()启动了计算绑定操作”);
if(tempId==4)
{
抛出新的ApplicationException(“从计算绑定操作中为id=4抛出错误”);
}
睡眠(20);
}
专用静态队列(ConcurrentQueue队列)
{
WriteLine(“枚举队列项:-”);
foreach(队列中的变量项)
{
Console.WriteLine(item.Id.ToString()+(!string.IsNullOrWhiteSpace(item.ErrorMessage)?item.ErrorMessage:“无错误”);
}
}
没有理由这样做:
/*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);
所说的,考虑到每个项目都在记录日志值(异常或成功),您可能需要考虑将此写入一个方法,然后将整个事件调用为PLIQ查询。
例如,如果将其写入处理try/catch(无线程)的方法,并返回“记录的字符串”,即: 您可以将整个操作编写为:var results = theIDs.AsParallel().Select(id => ProcessItem(id));
Parallel.ForEach
并发运行操作/func,最多可同时运行一定数量的实例。如果这些迭代中的每一个所做的不是本质上相互独立的,那么您就不会获得任何性能提升。而且,可能会通过引入昂贵的上下文切换和争用来降低性能。你说你想做一个“数据库调用”,并用一个文件操作来模拟它。如果每次迭代都使用相同的资源(例如,数据库表中的同一行;或者尝试写入同一位置的同一文件),那么它们实际上不会并行运行。一次只运行一个,其他的将只是“等待”获取资源——不必要地使代码复杂化
您还没有详细说明每个迭代要做什么;但是当我在其他程序员身上遇到这种情况时,他们几乎总是不是真正并行地做事情,他们只是简单地用
parallel.foreach
替换foreach
,希望神奇地获得性能或神奇地利用多CPU/核心处理器。您可能想从线程代码中删除控制台.WriteLine
。原因是每个windows应用程序只能有一个控制台。所以,若两个或多个线程将并行写入控制台,那个么其中一个线程必须等待
作为自定义错误队列的替代,您可能希望看到并捕获该错误队列,并相应地处理异常。InnerExceptions
属性将为您提供必要的异常列表。更多
还有一个一般的代码评论,不要在
if(tempId==4)
中使用像4
这样的幻数,而是定义一些常量,告诉4代表什么。e、 g.if(tempId==Error.FileMissing)
对Thread.Sleep()的调用是怎么回事?这“神奇地”让它工作了吗?除了实际的答案,这个代码有点混乱。魔法数字4怎么了?为什么要尝试嵌套块?为什么要忽略所有异常?…@Ankush-这个数字只是为了模拟我们可能会在某个项目上遇到异常。@Peter-Thread.Sleep()是为了模拟一些计算绑定的工作。让我备份一下-我执行“Task.WaitAll”的唯一原因是为了在FileAccess方法期间捕获任何异常(如果有)。有更好的异常处理方法吗?@AshishGupta您可以使用task.Wait()
-但是如果您按照我的建议序列化ti,您不必这样做-异常将直接在该线程中引发请查看我更新的问题。在每次迭代中,我都尝试执行一个计算绑定任务和一个数据库调用(此处为I/O模拟的文件访问)任务。我一直认为通过异步进行I/O总是比使用异步方式更好
var results = theIDs.AsParallel().Select(id => ProcessItem(id));