我想知道某些并行任务何时完成。
我正在使用此代码在网站上制作1500到2000个小型WebClient.DownloadString并使用10秒HttpRequest超时:
Task.Factory.StartNew(() =>
Parallel.ForEach<string>(myKeywords, new ParallelOptions
{ MaxDegreeOfParallelism = 5 }, getKey));
有时,查询失败,因此存在异常并且函数永远不会完成,并且每个getKey函数内的UI刷新有时似乎被调用两次,因此我无法准确了解完成了多少任务。我正在计算:UI刷新调用次数/关键字总数,得到100%到250%之间的结果,我永远不知道任务何时完成。我在很多SO讨论中搜索,但没有一个是直接的方法或方法,以满足我的需要。所以我猜Framework 4.0没有提供任何Tasks.AllCompleted事件处理程序或类似的解决方法?
我应该在另一个线程而不是我的UI线程中运行Parallel.Foreach然后添加它吗?
myTasks.WaitAll
[编辑]
临时解决方案是在ArrayList中复制我的字符串列表,然后在每个查询开头的列表中逐个删除每个项目。只要功能运行良好,我就知道所有项目都已处理完毕。
答案 0 :(得分:1)
Parallel.ForEach
is no different than other loops when it comes to handling exceptions。如果抛出异常,那么它将停止处理循环。这可能是你看到百分比差异的原因(我假设你在处理循环时可能正在处理计数)。
此外,您真的不需要Parallel.ForEach
因为您在WebClient
class上进行的异步调用将阻止等待IO completion(网络响应),他们不受计算限制(Parallel.ForEach
在计算限制时要好得多。)
也就是说,您应首先将来电转换为WebClient
以使用Task<TResult>
。使用event-based asynchronous pattern将task-based asynchronous pattern翻译为TaskCompletionSource<TResult>
class很简单。
假设您有一系列Uri
个实例是由于您调用getKey
而产生的,您可以创建一个函数来执行此操作:
static Task<String> DownloadStringAsync(Uri uri)
{
// Create a WebClient
var wc = new WebClient();
// Set up your web client.
// Create the TaskCompletionSource.
var tcs = new TaskCompletionSource<string>();
// Set the event handler on the web client.
wc.DownloadStringCompleted += (s, e) => {
// Dispose of the WebClient when done.
using (wc)
{
// Set the task completion source based on the
// event.
if (e.Cancelled)
{
// Set cancellation.
tcs.SetCancelled();
return;
}
// Exception?
if (e.Error != null)
{
// Set exception.
tcs.SetException(e.Error);
return;
}
// Set result.
tcs.SetResult(e.Result);
};
// Return the task.
return tcs.Task;
};
注意,上面的内容可以优化使用 one WebClient
,这是留给你的练习(假设你的测试表明你需要它)。
从那里,您可以获得一系列Task<string>
:
// Gotten from myKeywords
IEnumerable<Uri> uris = ...;
// The tasks.
Task<string>[] tasks = uris.Select(DownloadStringAsync).ToArray();
请注意,必须调用ToArray
extension method才能开始运行任务。这是为了解决deferred execution。您不必调用ToArray
,但必须调用将枚举整个列表并使任务开始运行的内容。
拥有这些Task<string>
个实例后,您可以通过调用ContinueWhenAll<TAntecedentResult>
method上的TaskFactory
class来完成所有这些操作,如下所示:
Task.Factory.ContinueWhenAll(tasks, a => { }).Wait();
完成此操作后,您可以遍历tasks
数组并查看Exception
和/或Result
属性,以查看异常或结果是什么。
如果您要更新用户界面,那么您应该查看拦截对Enumerable.Select的呼叫,即您应该调用Task<TResult>
上的ContinueWith<TNewResult>
method来执行操作下载完成时,如下:
// The tasks.
Task<string>[] tasks = uris.
Select(DownloadStringAsync).
// Select receives a Task<T> here, continue that.
Select(t => t.ContinueWith(t2 => {
// Do something here:
// - increment a count
// - fire an event
// - update the UI
// Note that you have to take care of synchronization here, so
// make sure to synchronize access to a count, or serialize calls
// to the UI thread appropriately with a SynchronizationContext.
...
// Return the result, this ensures that you'll have a Task<string>
// waiting.
return t2;
})).
ToArray();
这将允许您在事情发生时进行更新。请注意,在上述情况下,如果再次调用Select
,您可能需要检查t2
的状态并触发其他一些事件,具体取决于您希望错误处理机制的用途。