Effective C# 学习笔记(三十七) 警惕并行处理中的异常处理

若一个异常到达了调用该线程的方法时,该线程也就终止运行了。而并行编程使用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异常。

原文地址:https://www.cnblogs.com/haokaibo/p/2117783.html