zoukankan      html  css  js  c++  java
  • 记一次Task抛异常,调用线程处理而引发的一些随想

    记一次Task抛异常,调用线程处理而引发的一些随想

    多线程调用,任务线程抛出异常如何在另一个线程(调用线程)中捕获并进行处理的问题。

    1.任务线程在任务线程执行语句上抛出异常。

    例如:

     1   private void button2_Click(object sender, EventArgs e)
     2         {
     3             try
     4             {
     5                 var task = Task.Factory.StartNew<bool>(() =>
     6                 {
     7                     //Do Some Things 
     8                     throw new Exception("Task Throw Exception!");
     9                     //return true;
    10                 });
    11 
    12                 //var result = task.Wait(20000);
    13                 var result = task.Result;
    14             }
    15             catch (Exception ex)
    16             {
    17                 
    18             }
    19 
    20         }

    调试结果:在Task.Rrsult或者Wait时可以抛出任务异常,并在调用线程中通过try-catch捕获处理。

     2.任务线程在异步委托执行语句上抛出异常。

     1      private void button3_Click(object sender, EventArgs e)
     2         {
     3             var fun = new Func<int>(() =>
     4               {
     5                   //do sonmething
     6                   throw new Exception("Task Throw Exception!");
     7                   return 1;
     8               });
     9             try
    10             {
    11                 var task = Task.Factory.StartNew<bool>(() =>
    12                 {
    13                     try
    14                     {
    15                         var res = fun.BeginInvoke(null, null);
    16                         //do some thing
    17                         var ob = fun.EndInvoke(res);
    18                     }
    19                     catch (Exception ex)
    20                     {
    21 
    22                         throw ex;
    23                     }
    24                     return true;
    25                 });
    26                 var result = task.Wait(20000);
    27                 //var result1 = task.Result;
    28             }
    29             catch (Exception ex)
    30             {
    31 
    32             }
    33         }

    调试可知:异步委托在调用EndInvoke(res)获取结果时可以捕获委托内部异常并抛出由外部Task抓取。

     2.任务线程在窗口句柄(创建控件)线程上抛异常现象。

    control.invoke(参数delegate)方法:在拥有此控件的基础窗口句柄的线程上执行指定的委托。

    control.begininvoke(参数delegate)方法:在创建控件的基础句柄所在线程上异步执行指定委托。

    即invoke表是同步、begininvoke表示异步。但是如何来进行同步和异步呢?

     2.1Invoke方法执行规则

     Invoke的原理是借助消息循环通知主线程,并且在主线程执行委托。直接代码查看:

     1  private void button1_Click(object sender, EventArgs e)
     2         {
     3             //Invoke的原理是借助消息循环通知主线程,并且在主线程执行委托。
     4             try
     5             {
     6                 var thIdMain = Thread.CurrentThread.ManagedThreadId;
     7                 Console.WriteLine($"Load start: Main Thread ID:{thIdMain}");
     8                 var task = Task.Factory.StartNew<bool>(() =>
     9                 {
    10                     var taskId = Thread.CurrentThread.ManagedThreadId;
    11                     Console.WriteLine($"Task start: Task Thread ID:{taskId}");
    12                     var res = this.Invoke(new Func<int>(() =>
    13                     {
    14                         var InvokeId = Thread.CurrentThread.ManagedThreadId;
    15                         Console.WriteLine($"Invoke in: Begion Invoke Thread ID:{InvokeId}");
    16                         //do sonmething
    17                         return 1;
    18                     }));
    19                     taskId = Thread.CurrentThread.ManagedThreadId;
    20                     Console.WriteLine($"Invoke out ,Thread ID:{taskId}");
    21                     return true;
    22 
    23                 });
    24                
    25                 thIdMain = Thread.CurrentThread.ManagedThreadId;
    26                 Console.WriteLine($"Wait: Main Thread ID:{thIdMain}");
    27                 var CanLoad = task.Wait(2000);//.Result;
    28                 thIdMain = Thread.CurrentThread.ManagedThreadId;
    29                 Console.WriteLine($"End: Main Thread ID:{thIdMain}");
    30             }
    31             catch (Exception) { }
    32         }

    执行输出:

    Load start: Main Thread ID:1
    Wait: Main Thread ID:1
    Task start: Task Thread ID:3
    End: Main Thread ID:1
    Invoke in: Begion Invoke Thread ID:1
    Invoke out ,Thread ID:3

    查看输出顺序说明:invoke在主线程中执行,但是,invoke后面的代码必须在Invoke委托方法执行完成后,才能继续执行;而invoke在主线程中执行,所以其执行时机无法确定,得等消息循环(主线程)中其它消息执行后才能进行。

     2.2BeginInvoke方法执行规则

     BeginInvoke也在主线程执行相应委托。直接代码查看:

     1       private void button1_Click(object sender, EventArgs e)
     2         {
     3             //BeginInvoke
     4             try
     5             {
     6                 var thIdMain = Thread.CurrentThread.ManagedThreadId;
     7                 Console.WriteLine($"Load start: Main Thread ID:{thIdMain}");
     8                 var task = Task.Factory.StartNew<bool>(() =>
     9                 {
    10                     var taskId = Thread.CurrentThread.ManagedThreadId;
    11                     Console.WriteLine($"Task start: Task Thread ID:{taskId}");
    12                     var res = this.BeginInvoke(new Func<int>(() =>
    13                     {
    14                         var BegionInvokeId = Thread.CurrentThread.ManagedThreadId;
    15                         Console.WriteLine($"BeginInvoke in: Begion Invoke Thread ID:{BegionInvokeId}");
    16                         //do sonmething
    17                         return 1;
    18                     }));
    19                     taskId = Thread.CurrentThread.ManagedThreadId;
    20                     Console.WriteLine($"BeginInvoke is Completed: {res.IsCompleted}, Thread ID:{taskId}");
    21                     var ob = this.EndInvoke(res);
    22                     taskId = Thread.CurrentThread.ManagedThreadId;
    23                     Console.WriteLine($"BeginInvoke out ,Thread ID:{taskId}");
    24                     // Console.WriteLine(ob.ToString());
    25                     return true;
    26                 });
    27                 long i = 0;
    28                 while (i < 1000000000)//延时
    29                 {
    30                     i++;
    31                 }
    32                 thIdMain = Thread.CurrentThread.ManagedThreadId;
    33                 Console.WriteLine($"Wait: Main Thread ID:{thIdMain}");
    34                 //var CanLoad = task.Wait(2000);//.Result;
    35                 thIdMain = Thread.CurrentThread.ManagedThreadId;
    36                 Console.WriteLine($"End: Main Thread ID:{thIdMain}");
    37                 //Console.WriteLine(CanLoad);
    38             }
    39             catch (Exception) { }
    40         }

    执行输出:

    Load start: Main Thread ID:1
    Task start: Task Thread ID:3
    BeginInvoke is Completed: False, Thread ID:3
    Wait: Main Thread ID:1
    End: Main Thread ID:1
    BeginInvoke in: Begion Invoke Thread ID:1
    BeginInvoke out ,Thread ID:3

    根据输出结果可知begininvoke所提交的委托方法也是在主线程中执行,BeginInvoke is Completed: False, Thread ID:3与Wait: Main Thread ID:1两段比较,会发现begininvoke提交委托方法后,子线程继续执行,不需要等待委托方法的完成。

    总结:invoke和begininvoke都是在主线程中执行。invoke提交的委托方法执行完成后,才能继续执行;begininvoke提交委托方法后,子线程继续执行。invoke(同步)和begininvoke(异步)的含义,是相对于子线程而言的,实际上对于控件的调用总是由主线程来执行。

     2.3 Control.BeginInvoke或者Control.Invoke执行委托时抛出异常

    Control.Invoke执行委托时抛出异常:

     1   private void button2_Click(object sender, EventArgs e)
     2         {
     3             try
     4             {
     5                 var task = Task.Factory.StartNew<bool>(() =>
     6                 {
     7                     try
     8                     {
     9                     //Do Some Things 
    10                     var res = this.Invoke(new Func<int>(() =>
    11                     {
    12                         //do sonmething
    13                         throw new Exception("Task Throw Exception!");
    14                         return 1;
    15                     }));
    16                     }
    17                     catch (Exception ex)
    18                     {
    19 
    20                         throw ex;
    21                     }
    22                     return true;
    23                 });
    24             }
    25             catch (Exception ex)
    26             {
    27                 
    28             }
    29         }

    执行结果:只有task中的try可以捕捉,继续抛出,主线程捕捉不到 

     原因分析:button2_Click方法和task中invoke都是在主线程中执行。但是,invoke必须等主线程中其它消息执行完即button2_Click代码执行完退出才有机会执行。此时button2_Click方法执行完,所分配的内存空间被回收(失效),故即便task继续抛异常均不能捕获到。而Invoke在执行完成时,task后续代码阻断并等待其执行完,后续执行代码与其在内存上属于同一 堆栈,故可以捕获到Invoke抛出的异常。

    Control.BeginInvoke执行委托时抛出异常:

     1    private void button2_Click(object sender, EventArgs e)
     2         {
     3             try
     4             {
     5                 var task = Task.Factory.StartNew<bool>(() =>
     6                 {
     7                     try
     8                     {
     9                     //Do Some Things 
    10                     var res = this.BeginInvoke(new Func<int>(() =>
    11                     {
    12                         //do sonmething
    13                         throw new Exception("Task Throw Exception!");
    14                         return 1;
    15                     }));
    16 
    17                         var ob = this.EndInvoke(res);
    18                     }
    19                     catch (Exception ex)
    20                     {
    21 
    22                         throw ex;
    23                     }
    24                     return true;
    25                 });
    26             }
    27             catch (Exception ex)
    28             {
    29                 
    30             }
    31 
    32         }

    执行结果:均无法捕捉异常。但是Main函数中可以。

      原因分析:button2_Click方法和task中BeginInvoke都是在主线程中执行。但是,BeginInvoke须等主线程中其它消息执行完即button2_Click代码执行完退出才有机会执行。此时button2_Click方法执行完,所分配的内存空间被回收(失效),故即便task继续跑异常均不能捕获到。而BeginInvoke在执行完成时,task后续代码无须阻断等待其执行完,二者在内存上不属于同一 堆栈, 而异步调用时,异步执行期间产生的异常由CRL库捕获,你并一般在调用EndInvoke函数获取执行结果时CRL会抛出引发异步执行期间产生的异常,但是,CRL对Control.BeginInvoke特殊处理并未抛出(个人猜想,待验证)故此时Task无法捕获到BeginInvoke抛出的异常。

     一般BeginInvoke与Invoke主要用于更新控件相关属性值,特意抛异常的可能性应该比较小,如果有异常可以在该委托里面就进行解决了。此处仅作对技术研究的一个记录。

  • 相关阅读:
    常用Dos 命令
    Expect: 100continue
    Sql Server 文件和文件组体系结构
    Build Action
    regasm.exe 程序集注册工具
    获得user account的SID,GUID
    sp_change_users_login
    Regsvr32
    ASP.NET IIS 注册工具 (Aspnet_regiis.exe)
    随机生成300道四则运算
  • 原文地址:https://www.cnblogs.com/VueDi/p/11527443.html
Copyright © 2011-2022 走看看