C#多线程异常处理(三)
一、异常的抛出与进程终止
为了简化开发者基于task进行异步编程的难度, .NET Framework4.5改变了未监测异常的默认行为,尽管未监测异常依然会触发UnobservedTaskException异常,但进程默认情况下不再会终止。取而代之的是,异常触发后,运行时会自动处理,不管事件处理器是否检测到了异常,都会将其忽略而不造成进程终止。从.NET Framework 4.5开始,.NET Framework 4中的进程终止机制都可以通过配置被捕捉。
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
二、AggregateException
AggregateException类的作用是将多个异常集中到一个可抛出的异常中。
它有个InnerExceptions属性,是一个只读集合类,可通过遍历该集合查找集中的所有单个异常。
catch (AggregateException ag)
{
foreach (var item in ag.InnerExceptions)
{
Console.WriteLine("*********集合异常***********");
Console.WriteLine(item.ToString());
Console.WriteLine("*********集合异常***********");
}
}
其有两个重要的方法Handle()和Flatten()
Handle为AggregateException中包含的每个异常都调用一个回调方法。回调方法可以为每个异常决定如何处理;回调返回true表示异常已处理;false表示未处理。调用Handle后,如果至少有一个异常没有处理,就创建一个新的AggregateException对象,其中只包含未处理的异常,并抛出这个新的AggregateException对象。
catch (AggregateException ae) {
ae.Handle((x) =>
{
if (x is UnauthorizedAccessException) // This we know how to handle.
{
Console.WriteLine("You do not have permission to access all folders in this path.");
Console.WriteLine("See your network administrator or try another path.");
return true;
}
return false; // Let anything else stop the application.
});
}
AggregateException是聚合异常的,而Flatten方法的作用是将异常展开,并重新抛出,有助于客户端的处理。
catch (AggregateException ag)
{
throw ag.Flatten();
}
三、TaskScheduler.UnobservedTaskException事件
class Program
{
static void Main(string[] args)
{
//AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
try
{
//端口错误,连接数据库必然报错
string conn = "host=localhost;user = root; password = sheng; port = 3600; database = groupcustomerhotline_log;Character Set=utf8;pooling=true;Connection Timeout=200;Connection Lifetime=200;";
string sql = "select * from `tnet_activealarm` limit 3";
StringBuilder sb = new StringBuilder();
// int a = Convert.ToInt32(sql);//此异常不由异步操作触发,触发类型不为AggregateException。只有由task处罚的异常才会由AggregateException捕捉
TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs eventArgs) =>
{
//eventArgs.SetObserved();
foreach (var item in eventArgs.Exception.InnerExceptions)
{
Console.WriteLine("*********未监测异常***********");
Console.WriteLine(item.ToString());
Console.WriteLine("*********未监测异常***********");
}
};
//调用Continue不会等同于调用Result和Wait,触发的是未处理异常而不是集合异常,但是continuewith在不捕捉时会导致进程终止
MySqlHelper.ExecuteDatasetAsync(conn, sql)
.ContinueWith((task) =>
{
Console.WriteLine("Read Finished!");
Console.WriteLine(task.Result.Tables[0].Rows.Count);//此处能被捕捉,且不终止进程,这个地方为啥是未处理异常
foreach (DataRow row in task.Result.Tables[0].Rows)
{
sb = new StringBuilder();
foreach (DataColumn head in task.Result.Tables[0].Columns)//head
{
sb.Append(row[head]).ToString();
}
Console.WriteLine(sb.ToString());
}
});
var sqltask = MySqlHelper.ExecuteDatasetAsync(conn, sql);//这里的task中有错误,即使不被调用,也会被未处理异常捕捉
Action<string> ac = task => { Console.WriteLine(task); };//此处只是一个action,不涉及task
ac("hello");
Task.Run(() => { throw new NullReferenceException(); }).Wait();//此处能被捕捉,且不终止进程,因为没调用result或者wait,所以是未捕捉异常
sqltask.Wait();//能被捕捉,且终止进程
Action<Task<DataSet>> action = task => { Console.WriteLine(task.Result.Tables[0].Rows.Count); };//此处只是一个action,不涉及task
Task t = new Task((task) => { Console.WriteLine(((Task<DataSet>)task).Result.Tables[0].Rows.Count); }, sqltask);//能被捕捉,且不终止进程 //t.Start();
}
catch (AggregateException ag)
{
foreach (var item in ag.InnerExceptions)
{
Console.WriteLine("*********集合异常***********");
Console.WriteLine(item.ToString());
Console.WriteLine("*********集合异常***********");
}
}
catch (Exception ex)
{
Console.WriteLine("*********异常***********");
Console.WriteLine(ex.ToString());
Console.WriteLine("*********异常***********");
}
Thread.Sleep(100);//给异常抛出留有时间,避免此处执行完成后才抛出异常
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Aysnc Finished!");
Console.ReadLine();
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine("未捕捉异常!!!!!!");
string path = AppDomain.CurrentDomain.BaseDirectory + Path.DirectorySeparatorChar + "Log";
Directory.CreateDirectory(path);
using (StreamWriter sw = new StreamWriter(path+Path.DirectorySeparatorChar+"log.txt",true))
{
//sw.WriteAsync(e.ToString()).ContinueWith(()=> { sw.Flush(); });
sw.Write(e.ExceptionObject.ToString());
sw.Flush();
}
//Console.WriteLine(e.ExceptionObject.ToString());
Console.WriteLine("未捕捉异常!!!!!!");
}
private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
//throw new NotImplementedException();
Console.WriteLine("Fine unobserved exception..." + sender.ToString());
foreach (var iten in e.Exception.InnerExceptions)
{
Console.WriteLine(iten.ToString());
}
}
}
有时候该事件不会被触发,主要是事件处理器缺少以下语句
Thread.Sleep(100);//给异常抛出的时间
GC.Collect();//垃圾回收触发未检测异常的处理
GC.WaitForPendingFinalizers();
四、调用await后的Task异常处理
前面说过,Task对象通常抛出一个AggregateException,可查询该异常的InnerExceptions属性来查看真正发生了什么异常。但将await用于Task时,抛出的是第一个内部异常而不是AggregateException。这个设计提供了自然的编程体验。否则就必须在代码中捕捉AggregateException检查异步内部异常,然后要么处理异常要么重新抛出,这会过于繁琐。