In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article focuses on "what are the common problems in the use of C# asynchronous multithreading", interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn what are the common problems in the use of C# asynchronous multithreading.
Exception handling
Have you ever thought about how to handle multithreaded exceptions and how to handle exceptions in synchronous methods? you must be very familiar with them. What does multithreading look like? then I'll explain the exception handling of multithreading.
First, we define a task list. When 11 or 12 times, an exception is thrown, and the outermost package is packed with try catch.
Static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); try {TaskFactory taskFactory = new TaskFactory (); List tasks = new List (); for (int I = 0; I
< 20; i++) { string name = $"第 {i} 次"; Action action = t =>{Thread.Sleep (2 * 1000); if (name.ToString () .Equals ("11th")) {throw new Exception ($"{t}, execution failed") } if (name.ToString (). Equals ("12th")) {throw new Exception ($"{t}, execution failed");} Console.WriteLine ($"{t}, execution successful");}; tasks.Add (taskFactory.StartNew (action, name)) } catch (AggregateException aex) {foreach (var item in aex.InnerExceptions) {Console.WriteLine ("Main AggregateException:" + item.Message);}} catch (Exception ex) {Console.WriteLine ("Main Exception:" + ex.Message);} Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}") Console.ReadLine ();}
When you start the program, you can see that vs caught the exception line of code, but catch didn't catch the exception. Why? Because the exception in the thread has been swallowed, you can also see from the result of the run that the main end ends when the child thread does not execute anything, which means that the catch execution has passed.
Is there any way to catch multithreaded exceptions? Answer: yes, just wait for the thread to complete the calculation.
Look at the following code. There is a special place where AggregateException.InnerExceptions is specially prepared for multithreading. You can view multithreaded exception information.
Static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); try {TaskFactory taskFactory = new TaskFactory (); List tasks = new List (); for (int I = 0; I
< 20; i++) { string name = $"第 {i} 次"; Action action = t =>{Thread.Sleep (2 * 1000); if (name.ToString () .Equals ("11th")) {throw new Exception ($"{t}, execution failed") } if (name.ToString (). Equals ("12th")) {throw new Exception ($"{t}, execution failed");} Console.WriteLine ($"{t}, execution successful");}; tasks.Add (taskFactory.StartNew (action, name)) } Task.WaitAll (tasks.ToArray ());} catch (AggregateException aex) {foreach (var item in aex.InnerExceptions) {Console.WriteLine ("Main AggregateException:" + item.Message);}} catch (Exception ex) {Console.WriteLine ("Main Exception:" + ex.Message) } Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); Console.ReadLine ();}
When you start the thread, you can see that all the tasks have been executed, and the AggregateException.InnerExceptions stores the exception information when the child thread executes.
But WaitAll is not good, you can't WaitAll all the time, it will jam the interface. It doesn't apply to asynchronous scenarios, right? let's move on to another solution. That is, exceptions are not allowed in child threads. If you can handle them by yourself, that is, try catch package it. It is recommended to do this in normal work.
Use try catch to package the code executed by the child thread and print the error message in catch
Static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); try {TaskFactory taskFactory = new TaskFactory (); List tasks = new List (); for (int I = 0; I
< 20; i++) { string name = $"第 {i} 次"; Action action = t =>{try {Thread.Sleep (2 * 1000); if (name.ToString () .Equals ("11th")) {throw new Exception ($"{t}, execution failed") } if (name.ToString () .Equals ("12th")) {throw new Exception ($"{t}, execution failed");} Console.WriteLine ($"{t}, execution successful") } catch (Exception ex) {Console.WriteLine (ex.Message);}}; tasks.Add (taskFactory.StartNew (action, name)) } catch (AggregateException aex) {foreach (var item in aex.InnerExceptions) {Console.WriteLine ("Main AggregateException:" + item.Message);}} catch (Exception ex) {Console.WriteLine ("Main Exception:" + ex.Message);} Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}") Console.ReadLine ();}
When you start the program, you can see that all the tasks are executed and the child thread exception is also caught
Thread cancellation
Sometimes there is a scenario in which multiple tasks are executed concurrently, and if one task fails, the other tasks are told to stop. First of all, take a shot that Task cannot be terminated externally, and Thread.Abort is unreliable. In fact, the idea of thread cancellation is wrong. Threads are the resources of OS, and the program cannot control when to cancel. If an action is issued, it may be cancelled immediately, or it may wait for 1 s to cancel.
Solution: threads stop themselves, define common variables, modify the state of variables, and other threads constantly detect common variables
For example, CancellationTokenSource is a public variable, initialized to the false state, the program executes the CancellationTokenSource. Cancel () method, and other threads detect that the CancellationTokenSource. IsCdispensationRequested will be canceled. CancellationTokenSource.Token is passed in when starting Task, and if it is already CancellationTokenSource.Cancel (), the task will abort startup and throw an exception.
Static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); try {TaskFactory taskFactory = new TaskFactory (); List tasks = new List (); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource (); / / bool for (int I = 0; I
< 20; i++) { string name = $"第 {i} 次"; Action action = t =>{try {Thread.Sleep (2 * 1000); if (name.ToString () .Equals ("11th")) {throw new Exception ($"{t}, execution failed") } if (name.ToString () .Equals ("12th")) {throw new Exception ($"{t}, execution failed") } if (cancellationTokenSource.IsCancellationRequested) / / detect semaphore {Console.WriteLine ($"{t}, abort execution"); return;} Console.WriteLine ($"{t}, execution successful") } catch (Exception ex) {cancellationTokenSource.Cancel (); Console.WriteLine (ex.Message);}}; tasks.Add (taskFactory.StartNew (action, name,cancellationTokenSource.Token));} Task.WaitAll (tasks.ToArray ()) } catch (AggregateException aex) {foreach (var item in aex.InnerExceptions) {Console.WriteLine ("Main AggregateException:" + item.Message);}} catch (Exception ex) {Console.WriteLine ("Main Exception:" + ex.Message);} Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}") Console.ReadLine ();}
When you start the program, you can see that 11 and 12 of this task failed and 18 and 19 gave up the task. Some friends wonder why the execution of the part after 12 is successful, because CPU is time-sharing slicing, there will be delay, delay is necessary.
Temporary variable
First of all, take a look at the code, loop 5 times, multithreading, and output the serial number in turn.
Static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); for (int I = 0; I
< 5; i++) { Task.Run(() =>{Console.WriteLine (I);});} Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); Console.ReadLine ();}
The startup program is not the result we expected 0, 1, 2, 3, 4, why five 5s? Because there is only one I in the whole process, when the main thread finishes execution, I = 5, but the child thread may not start to execute the task yet. when it is the turn of the child thread to take I, it is already 5 after the end of the cycle of the main thread 1.
Modification code: add a line of code int k = I in the for loop, and the variable used in the child thread is also changed to k
Static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); for (int I = 0; I
< 5; i++) { int k = i; Task.Run(() =>{Console.WriteLine ($"k = {k}, I = {I}");} Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); Console.ReadLine ();}
When you start the program, you can see that it is our expected result 0, 1, 2, 3, 4. Why is this so? Because there are five k in the whole process, a k is created for each loop to store the current I, and different child threads use the I value for each loop.
Thread safety
Why is there the concept of thread safety in the first place? First, let's take a look at a normal program, as follows
Static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); int TotalCount = 0; List vs = new List (); for (int I = 0; I
< 10000; i++) { TotalCount += 1; vs.Add(i); } Console.WriteLine(TotalCount); Console.WriteLine(vs.Count); Console.WriteLine($"Main End,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}"); Console.ReadLine();} 启动程序,可以看到循环 10000 次,最终的求和与列表里的数据量都是 10000,这是正常的 接着,将求和与添加列表,换成多线程,等待全部线程完成工作后,打印信息 static void Main(string[] args){ Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.ManagedThreadId},Datetime:{DateTime.Now.ToLongTimeString()}"); int TotalCount = 0; List vs = new List(); TaskFactory taskFactory = new TaskFactory(); List tasks = new List(); for (int i = 0; i < 10000; i++) { int k = i; tasks.Add(taskFactory.StartNew(() =>{TotalCount + = 1; vs.Add (I);} Task.WaitAll (tasks.ToArray ()); Console.WriteLine (TotalCount); Console.WriteLine (vs.Count); Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); Console.ReadLine ();}
When you start the program, you can see that neither result is 10000? This is thread safety.
Because TotalCount is a shared variable, when multiple threads go to fetch TotalCount for + 1, and all threads put the value, the latter thread will replace the value placed by the previous thread, so it will result in a final result that is not 10000. The list, which can be thought of as a contiguous block, is also overwritten when multiple threads are added.
How to solve the problem? Answer: lock, security queue, split and merge computing. The following is an explanation of lock, security queue and split merge calculation, and interested partners can communicate privately.
1 .lock
First, through the way of locking, which is also commonly used in daily work. First define a private static reference type variable, and then put the operation that needs to be locked into the lock () method
Only one thread executes at the same time within {}, so put the necessary logic running as much as possible to improve efficiency. Lock can only lock the reference type by occupying the reference link. Do not use string to share elements, that is, if lock () is the same string, no matter how many variables are defined, it is actually one.
Internal class Program {private static readonly object _ lock = new object (); static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); int TotalCount = 0; List vs = new List (); TaskFactory taskFactory = new TaskFactory (); List tasks = new List (); for (int I = 0; I
< 10000; i++) { int k = i; tasks.Add(taskFactory.StartNew(() =>{lock (_ lock) {TotalCount + = 1; vs.Add (I);}}));} Task.WaitAll (tasks.ToArray ()); Console.WriteLine (TotalCount); Console.WriteLine (vs.Count) Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); Console.ReadLine ();}}
Starting the program, you can see that in the case of multithreading, the final result is normal.
This code is officially recommended to be written by private to prevent it from being quoted outside, and static ensures that it is the only one in the audience.
Private static readonly object _ lock = new object ()
Extension: there is a Monitor equivalent to lock, which is used as follows
Private static object _ lock = new object (); static void Main (string [] args) {Console.WriteLine ($"Main Start,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}"); int TotalCount = 0; List vs = new List (); TaskFactory taskFactory = new TaskFactory (); List tasks = new List (); for (int I = 0; I
< 10000; i++) { int k = i; tasks.Add(taskFactory.StartNew(() =>{Monitor.Enter (_ lock); TotalCount + = 1; vs.Add (I); Monitor.Exit (_ lock);} Task.WaitAll (tasks.ToArray ()); Console.WriteLine (TotalCount); Console.WriteLine (vs.Count); Console.WriteLine ($"Main End,ThreadId: {Thread.CurrentThread.ManagedThreadId}, Datetime: {DateTime.Now.ToLongTimeString ()}") Console.ReadLine ();}
At this point, I believe you have a deeper understanding of "what are the common problems in the use of C# asynchronous multithreading?" you might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.