记一次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主要用于更新控件相关属性值,特意抛异常的可能性应该比较小,如果有异常可以在该委托里面就进行解决了。此处仅作对技术研究的一个记录。
下一篇: DEV Chart控件鼠标选中某条曲线