Task Description
Microsoft in. net Framework 4.0 introduces a new class: system. thread. task, which is used to represent an asynchronous operation, such as downloading an object from the Internet or writing a time-consuming file. If you put these operations in the UI thread, this will cause the user interface to lose response, so we need to open another thread to do these things. Opening a thread is nothing new, but it is. net Framework 4.0, Microsoft uses the task to re-package it. The direct benefit of this re-packaging is, of course, the code is simpler. In addition, it will have better performance, because its underlying implementation uses a "Thread Pool", when a task needs to be executed, it will wake up the working thread; otherwise, the working thread will be transferred to sleep, saving the thread creation overhead, all of this can be transparent to our programmers. We can use it. Microsoft abstracts thread into task.
Simplest example
Let's take a simple example.
Using system; using system. threading; using system. threading. tasks; Class program {static void main (string [] ARGs) {task TASKA = task. factory. startnew () => dosomework (2000); TASKA. wait (); // The main thread is blocked here until task a completes the console. writeline ("TASKA has completed. ");} static void dosomework (INT Val) {thread. sleep (VAL); // use sleep to simulate a very time-consuming task }}
The code should not have much to explain. If you are not familiar with Lambda expressions, you may find it strange to see the "=>" symbol. There are many articles on the Internet about lambda expressions, of course, the most recommended article is the official Microsoft article lambda expressions (C # programming guide). If you have time, it will not take much time. If you do not have time, let me briefly talk about it here: it is actually an anonymous function. The above code can be written as follows:
Static void todotwosecondswork () {dosomework (2000);} static void main (string [] ARGs) {task TASKA = task. factory. startnew (todotwosecondswork); TASKA. wait (); // The main thread is blocked here until task a completes the console. writeline ("TASKA has completed. ");}
The effect is the same, but in this case, you need to write another function called "todotwosecondswork". It is also advantageous to explicitly write the function name, that is, to facilitate breakpoint debugging.
Tip: You can set the timeout wait time for the wait method. Once the wait time is reached, the system will return even if the task is not completed.
Continuewith
In the above example, the wait method is used to wait for the task to end. This method will actually block the main thread, that is to say, it will cause the UI to lose response, which is not in line with our original intention, how can this problem be solved? You can try this: continuewith, continuewith does not block the main thread, the main thread will continue to go down, and after the working thread completes the task, it will call a callback function.
static void Main(string[] args) { Task taskA = Task.Factory.StartNew(() => DoSomeWork(2000)); taskA.ContinueWith(t => Debug.WriteLine("taskA has completed.")); Console.ReadKey(); }
When task a ends, the message "TASKA has completed." will be output in the output window. Why can't I directly output the message on the console? This is because the execution thread of this output action is not the main thread. Although this is a console program, we should not directly use the Working thread to operate the UI in terms of design, for details about how the worker thread communicates with the UI, refer to another blog :《. net.
Multiple worker threads
Although the above example is also "multithreading", there is only one working thread. Sometimes we need to enable multiple working threads at the same time. For example, "multi-thread download" is a typical example, in addition, when doing some particularly time-consuming multimedia processing, we often split a processing into several parts so that they can be executed at the same time, taking full advantage of the current multi-core CPU advantages. The following is an example:
Task[] tasks = new Task[10]; for (int i = 0; i < 10; i++) { tasks[i] = Task.Factory.StartNew(() => DoSomeWork(2000)); } Task.WaitAll(tasks); Console.WriteLine("All tasks have completed.");
We have created a total of 10 tasks. Maybe you have to ask: are these 10 tasks executed at the same time? The answer is no ,. net Framework will automatically schedule them to be executed. If your computer has four cores, it is very likely that the CPU will schedule four tasks to be executed at the same time. In short, you cannot assume that these tasks are executed at the same time, therefore, when splitting a task, it is recommended that the task and the task be independent of each other. Do not wait for another task. Otherwise, the deadlock may easily occur.
Tip: If you only need to wait for the end of one of the tasks, you can try task. waitany.
Obtain results
See the following example:
static void Main(string[] args) { Task<int> taskA = Task<int>.Factory.StartNew(() => NumberSumWork(100)); taskA.Wait(); Console.WriteLine("The result is " + taskA.Result); } static int NumberSumWork(int iVal) { int iResult = 0; for (int i = 1; i <= iVal; i++) { Thread.Sleep(40); iResult += i; } return iResult; }
In fact, the property of result retrieval contains wait, so the above Code can also be written as follows:
static void Main(string[] args) { Task<int> taskA = Task<int>.Factory.StartNew(() => NumberSumWork(100)); Console.WriteLine("The result is " + taskA.Result); }
If you use continuewith, you can also write it as follows:
taskA.ContinueWith(t => Debug.WriteLine(t.Result));
Throw an exception
I believe the above content is well understood, but when it comes to exceptions, what should I do?
Let me tell you a story: I was working on a program a while ago. This program has a function, that is, to check the latest version online. Considering that this operation may take several seconds, I made it a non-blocking form, that is, using the "continuewith" mentioned above, so that the user interface can never lose response, even if the network is disconnected, after a few seconds, the user will be reminded on the interface that "failed to get the latest version ". This program runs on my computer without any problems, but when I get my colleague's computer, there is a problem. When the network is disconnected, the program will also "fail to get the latest version of information ", once this failure occurs, the program will crash as soon as the interface is switched. A similar problem occurs in another colleague, but the crash time is not the same, I cannot catch this exception even when I use global exception capture. After a lot of debugging, the problem cannot be reproduced on my computer. I am sure this is caused by the use of multiple threads, but I couldn't find the reason. Finally, I replaced continuewith with the blocking method, however, when performing the update check, the interface will lose response for a short time. I will analyze this matter later, and now I will return to the topic.
Let's take a look at the simplest Exception Handling example:
class MyException:Exception{ public MyException(string message) :base(message) { }}class Program{ static void Main(string[] args) { Task taskA = Task.Factory.StartNew(()=>DoSomeWork(2000)); taskA.Wait(); Console.WriteLine("taskA has completed."); } static void DoSomeWork(int val) { Thread.Sleep(val); throw new MyException("This is a test exception."); }}
Debugging, an exception occurs, and the execution point stays at the wait location:
Exception type: aggresponexception:
An error occurred. The exception type is not exception, but aggresponexception. Why? Because this is an exception generated by other threads and there may be more than one exception, we need to use the aggresponexception class to act as an exception container. The exceptions generated by the working thread will be placed in this container, finally, let's process this container. After the code above is changed, an aggresponexception containing multiple exceptions can be generated.
class Program{ static void Main(string[] args) { Task[] tasks = new Task[2]; tasks[0] = Task.Factory.StartNew(() => DoSomeWork(2000)); tasks[1] = Task.Factory.StartNew(() => DoSomeWork(3000)); Task.WaitAll(tasks); Console.WriteLine("Tasks have completed."); } static void DoSomeWork(int val) { Thread.Sleep(val); throw new MyException(string.Format("This is a test exception."+val)); }}
For processing aggresponexception, you can do this:
Try {task. waitall (tasks);} catch (aggresponexception AE) {AE. handle (x => {If (X is myexception) {console. writeline ("exception handled:" + X. message); return true; // indicates that the exception has been handled} return false; // indicates that the exception has not been processed and the exception is still thrown });}
The result is displayed as follows:
Exception handled: this is a test exception.2000
Exception handled: this is a test exception.3000
Continuewith exception
As mentioned above, continuewith is non-blocking, and try-Catch cannot catch aggresponexception. So where are the exceptions generated by the task? Now let's think about the story I mentioned earlier: When I use continuewith, once the network connection is wrong, the program will crash at some time (such as when the interface is switched) on my colleagues' computers, this problem cannot be reproduced on my computer. In this case, the exception (network connection exception) produced by continuewith was inexplicably ignored on my computer, but was treated as an unprocessed exception on my colleagues' computer. Now, I want to simplify my program into the following code (complete code ):
using System;using System.Diagnostics;using System.Threading;using System.Threading.Tasks;class TestException:Exception{ public TestException(string message) :base(message) { }}class Program{ static void Main(string[] args) { Task<int> taskA = Task<int>.Factory.StartNew(() => DoSomeWork(2000)); taskA.ContinueWith(t => Debug.WriteLine("taskA has completed. result is " + taskA.Result)); Console.ReadKey(); GC.Collect(); Console.ReadKey(); } static int DoSomeWork(int val) { Thread.Sleep(val); throw new TestException("This is a test exception."); return val; }}
Run this code. Wait for two seconds and you will find that the program has not reported any exceptions, although you can see "a first chance exception... "Such an exception prompt, but the program is still safe and sound. on my computer, if I touch the Enter key twice, the program will quit without any error message, but on my colleague's computer, the following error occurs when you touch the Enter key in the last two seconds:
What is this! This kind of problem cannot be solved by yourself. You can only search for the answer on the Internet. Please refer to the link: Application compatibility in the. NET Framework 4.5.
It includes the following content:
- Feature: Unobserved exceptions in task operations
- Change: Because the task class represents an asynchronous operation, it catches all non-severe exceptions that occur during asynchronous processing. in. net Framework 4.5, if an exception is not observed and your code never waits on the task, the exception will no longer propagate on the finalizer thread and crash the process during garbage collection.
- Impact: This change enhances the reliability of applications that use the task class to perform unobserved asynchronous processing. The previous behavior can be restored by providing an appropriate handler for the taskscheduler. unobservedtaskexception event.
For the handling of unobtained exceptions in asynchronous task operations,. NET Framework 4.5 and. NET Framework 4.0 are different, and 4.5 ignores them, while 4.0 takes them seriously. This is why my program cannot reproduce the error on my computer. I have installed. NET Framework 4.5 and my colleague's computer is still 4.0.
Maybe you have another question: why should we add the "GC. Collect ()" statement? Have you noticed that? When you run this program, if you do not move any key, it will not report an exception. It will be reported only when you move it, this is because such an aggresponexception is thrown when garbage collection is executed. This is why my program crashes during "interface switching" on my colleagues' computers, because at that time, it is very likely (not necessarily) that a garbage collection will be executed, so the crash time point does not seem so fixed, because the garbage collection is actually caused. net Framework is automatically executed, so we should not intervene in it.
What should we do if we want to handle this "ignored" exception? The above example is changed as follows:
TASKA. continuewith (t => {If (! T. isfaulted) {debug. writeline ("TASKA has completed. result is "+ TASKA. result);} else {debug. writeline ("exception:" + T. exception); // log exception T. exception. handle (x => true); // and mark the exception as handled }});
In this way, no exceptions will occur in. NET Framework 4.0. To be compatible with. NET Framework 4.0, you must use this method.