Task中的异常处理

一、异常的抛出与进程终止
为了简化开发者基于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检查异步内部异常,然后要么处理异常要么重新抛出,这会过于繁琐。

猜你喜欢

转载自blog.csdn.net/u010178308/article/details/80737332