博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
记一次Task抛异常,调用线程处理而引发的一些随想
阅读量:3964 次
发布时间:2019-05-24

本文共 9195 字,大约阅读时间需要 30 分钟。

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

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

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

例如:

1   private void button2_Click(object sender, EventArgs e) 2         { 3             try 4             { 5                 var task = Task.Factory.StartNew
(() => 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
(() => 4 { 5 //do sonmething 6 throw new Exception("Task Throw Exception!"); 7 return 1; 8 }); 9 try10 {11 var task = Task.Factory.StartNew
(() =>12 {13 try14 {15 var res = fun.BeginInvoke(null, null);16 //do some thing17 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
(() => 9 {10 var taskId = Thread.CurrentThread.ManagedThreadId;11 Console.WriteLine($"Task start: Task Thread ID:{taskId}");12 var res = this.Invoke(new Func
(() =>13 {14 var InvokeId = Thread.CurrentThread.ManagedThreadId;15 Console.WriteLine($"Invoke in: Begion Invoke Thread ID:{InvokeId}");16 //do sonmething17 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
(() => 9 {10 var taskId = Thread.CurrentThread.ManagedThreadId;11 Console.WriteLine($"Task start: Task Thread ID:{taskId}");12 var res = this.BeginInvoke(new Func
(() =>13 {14 var BegionInvokeId = Thread.CurrentThread.ManagedThreadId;15 Console.WriteLine($"BeginInvoke in: Begion Invoke Thread ID:{BegionInvokeId}");16 //do sonmething17 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
(() => 6 { 7 try 8 { 9 //Do Some Things 10 var res = this.Invoke(new Func
(() =>11 {12 //do sonmething13 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
(() => 6 { 7 try 8 { 9 //Do Some Things 10 var res = this.BeginInvoke(new Func
(() =>11 {12 //do sonmething13 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主要用于更新控件相关属性值,特意抛异常的可能性应该比较小,如果有异常可以在该委托里面就进行解决了。此处仅作对技术研究的一个记录。

 

转载地址:http://ycuki.baihongyu.com/

你可能感兴趣的文章
Java 变量名
查看>>
Java 四舍五入运算
查看>>
Spring Batch 例子: 运行系统命令
查看>>
解析输入
查看>>
格式化输出
查看>>
Java 大数值
查看>>
括号及后向引用
查看>>
Spring Batch 核心概念
查看>>
Spring Batch 例子: 导入分隔符文件到数据库
查看>>
非贪婪匹配
查看>>
Spring Batch 例子: 导入定长文件到数据库
查看>>
匹配时刻
查看>>
为数值添加逗号
查看>>
忽略大小写匹配
查看>>
全局匹配模式
查看>>
Java 日期时间
查看>>
Java 字符串
查看>>
Spring Batch 例子: 从数据库导出分隔符文件
查看>>
元字符终极总结
查看>>
八进制转义
查看>>