欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-14 16:40:21
多线程调用,任务线程抛出异常如何在另一个线程(调用线程)中捕获并进行处理的问题。 ......

记一次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捕获处理。

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

 

 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可以捕捉,继续抛出,主线程捕捉不到 

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

 

 原因分析: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函数中可以。

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

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

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