若一个异常到达了调用该线程的方法时,该线程也就终止运行了。而并行编程使用AggregateException类型来处理发生在子线程中的各类异常,而这些异常存储在AggregateException对象的InnerExceptions属性中。处理异常策略的原则是:处理你能恢复到正常状态的异常,抛出其他那些异常。
下面的代码,是修改上一次笔记中说明Web下载逻辑。使用一个Dictionary结构来处理异常,其键值分别为异常类型(Exception Type)和处理的代理(Action<T>)。
//异常处理段代码
try
{
urls.RunAsync(url => startDownload(url),
task => finishDownload(task.AsyncState.ToString(),
task.Result));
}
catch (AggregateException problems)
{
//注册处理各类异常的逻辑
var handlers = new Dictionary<Type, Action<Exception>>();
handlers.Add(typeof(WebException),ex => Console.WriteLine(ex.Message));
if (!HandleAggregateError(problems, handlers))
throw;//这里抛出了AggregateException,而不是具体的异常,因为InnerExceptions中可能有你需要的其他异常信息。
}
//处理异常的方法
private static bool HandleAggregateError(AggregateException aggregate,
Dictionary<Type, Action<Exception>> exceptionHandlers)
{
foreach (var exception in aggregate.InnerExceptions)
//递归处理所有的内部异常
if (exception is AggregateException)
return HandleAggregateError(
exception as AggregateException, exceptionHandlers);
//若包含处理方法,处理之
else if (exceptionHandlers.ContainsKey( exception.GetType()))
{
exceptionHandlers[exception.GetType()] (exception);
}
//否则处理不了,返回false
else
return false;
return true;
}
在大多数情况下,处理已知异常,而不是抛出它,不处理它更合适。所以我们修改了开始下载处理逻辑部分的代码:
private static Task<byte[]> startDownload(string url)
{
var tcs = new TaskCompletionSource<byte[]>(url);
var wc = new WebClient();
wc.DownloadDataCompleted += (sender, e) =>
{
if (e.UserState == tcs)
{
if (e.Cancelled)
tcs.TrySetCanceled();
else if (e.Error != null)
{
if (e.Error is WebException)//当发生WebException时,将结果设置为0字节
tcs.TrySetResult(new byte[0]);
else//其他异常情况
tcs.TrySetException(e.Error);
}
else//正常情况
tcs.TrySetResult(e.Result);
}
};
wc.DownloadDataAsync(new Uri(url), tcs);
return tcs.Task;
}
上面的代码在处理WebException时,只返回0字节,表示下载的失败,因为该异常明确的说明了服务地址的不可达。
由于Query查询只在有代码访问其结果集的时候会执行,所以不必在定义Query的地方加入try/catch区块,你只需在执行获取Query结果集的部分执行即可。如下代码:
var nums = from n in data
where n < 150
select Factorial(n);
try
{
foreach (var item in nums)
Console.WriteLine(item);
}
catch (InvalidOperationException inv)
{
// elided
}
而在使用PLINQ时,由于其执行顺序的不同,你需要把定义Query的部分也try/catch起来,而你捕获的异常要用AggregateException多线程并发异常类,其内部属性InnerExceptions会返回你要的内部异常,其是由Parallel Task Library提供专门用来处理并发运行中的异常的。若任何一个后台线程抛出异常,整个的后台操作也就停止了。所以你要做的是尽量确保后台不抛出异常,并处理AggregateException异常。