Remember some random thoughts caused by Task throwing exception and calling thread processing

Keywords: C# less

Remember some random thoughts caused by Task throwing exception and calling thread processing

Multithreaded call, the problem how to catch and handle in another thread (calling thread) when the task thread throws an exception.

1. The task thread throws an exception on the execution statement of the task thread.

For example:

 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         }

Debugging result: when Task.Rrsult or Wait, the task exception can be thrown and handled through try catch in the calling thread.

 

2. The task thread throws an exception on the asynchronous delegate execution statement.

 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         }

Debugging shows that when an asynchronous delegate calls EndInvoke(res) to get the result, it can catch the internal exception of the delegate and throw it to be grabbed by the external Task.

 

2. The task thread throws an exception on the window handle (create control) thread.

Control.invoke method: executes the specified delegate on the thread that owns the control's underlying window handle.

Control.begininvoke (parameter delegate) method: executes the specified delegate asynchronously on the thread where the basic handle of the control is created.

That is, the invoke table is synchronous and begininvoke is asynchronous. But how to synchronize and asynchronously?

2.1Invoke method execution rules

The principle of Invoke is to inform the main thread with message loop and execute the delegation in the main thread. Direct code view:

 1  private void button1_Click(object sender, EventArgs e)
 2         {
 3             //Invoke The principle is to inform the main thread with message loop and execute the delegation in the main thread.
 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         }

Execute output:

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

View the output sequence description: invoke is executed in the main thread, but the code behind invoke can only continue to execute after the invoke delegate method is executed; while invoke is executed in the main thread, so the execution time cannot be determined, and it can only be executed after other messages in the message loop (main thread) are executed.

2.2 execution rules of BeginInvoke method

BeginInvoke also performs the corresponding delegate on the main thread. Direct code view:

 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)//delayed
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         }

Execute output:

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

According to the output results, the delegation method submitted by begininvoke is also executed in the main thread. BeginInvoke is Completed: False, Thread ID:3 is compared with Wait: Main Thread ID:1. It will be found that after begininvoke submits the delegation method, the sub thread continues to execute without waiting for the completion of the delegation method.

Conclusion: both invoke and begininvoke are executed in the main thread. The execution of the delegate method submitted by invoke can not continue until it is completed; the execution of the sub thread can continue after begininvoke submits the delegate method. The meaning of invoke (synchronous) and begininvoke (asynchronous) is relative to the sub thread. In fact, the call to the control is always executed by the main thread.

2.3 exception thrown when control.begininvoke or Control.Invoke execute the delegation

Exception thrown when Control.Invoke executes delegate:

 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         }

Execution result: only try in the task can be caught, continue to throw, and the main thread cannot catch it

 

Cause analysis: both button2 click method and invoke in task are executed in the main thread. However, the invoke must wait for other messages in the main thread to finish executing, i.e. the button2 click code to finish executing before it can exit. At this time, when the button2 click method is completed, the allocated memory space is recycled (invalid), so even if the task continues to throw exceptions, it cannot be caught. When the execution of invoke is completed, the subsequent code of task blocks and waits for its execution. The subsequent execution code belongs to the same stack as its memory, so the exception thrown by invoke can be caught.

Control.BeginInvoke threw an exception while executing the delegate:

 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         }

Execution result: no exception can be caught. But in the Main function.

Cause analysis: button2 click method and BeginInvoke in task are executed in the main thread. However, BeginInvoke has to wait for other messages in the main thread to finish executing, that is, the button2 click code to finish executing before exiting. At this time, when the button2 click method is completed, the allocated memory space is recycled (invalid), so even if the task continues to run abnormally, it cannot be caught. When BeginInvoke is completed, the subsequent code of task does not need to be blocked to wait for its completion. They do not belong to the same stack in memory. When asynchronous call is made, the exception generated during asynchronous execution is caught by CRL library. When you call EndInvoke function to get execution result, CRL will throw the exception generated during asynchronous execution. However, CRL has no need to block Control.BeginInvoke Special handling is not thrown (personal guess, to be verified). Therefore, the task cannot catch the exception thrown by BeginInvoke at this time.

Generally, BeginInvoke and Invoke are mainly used to update the property values of the control. It is less likely to throw exceptions intentionally. If there are exceptions, they can be solved in the delegation. Only one record of technical research is made here.

Posted by fael097 on Wed, 08 Apr 2020 07:36:57 -0700