Abstract :. net provides many multi-threaded programming tools, probably because there are too many tools, so it is always a headache to master. Here I will talk about some of my multi-threaded programming experiences, hoping to help you. 1. the summary of multithreading does not require passing parameters or returning parameters. We know that the most intuitive way to start a Thread is to use the Thread class. The specific steps are as follows: public void test () {ThreadStart threadStart = new ThreadStart (Calculate); Thread thread = new Thread (threadStart); thread. start ();} public void Calculate () {double Diameter = 0.5; Console. write ("The perimeter Of Circle with a Diameter of {0} is {1}", Diameter, Diameter * Math. PI);} Example 1 above we used to define a ThreadStart type delegate, which formulated the method to be executed by the thread: Calcul In this method, the circumference of a circle with a diameter of 0.5 is calculated and output. this constitutes the simplest example of multithreading. In many cases, this is enough, and the ThreadStart delegate is defined as void ThreadStart (), that is, the method to be executed does not have parameters. This is obviously a huge deficiency. To make up for this defect, a smart programmer has come up with many good methods, we will introduce it in the section that needs to pass multiple parameters. Here we will first introduce it. net another delegate set to solve this problem: ParameterizedThreadStart, which will be detailed below. The public void test {// ParameterThreadStart parameter must be passed and is defined as void ParameterizedThreadStart (object state) // the startup function of the thread defined using this delegate can accept an input parameter, the following is an example of ParameterizedThreadStart threadStart = new ParameterizedThreadStart (Calculate); Thread thread = new Thread (); thread. start (0.9);} public void Calculate (object arg) {double Diameter = double (arg); Console. write ("The perimeter Of Circle with a Diameter of {0} is {1}", D Iameter, Diameter * Math. PI);} Example 2 the Calculate method has a parameter of the object type. Although there is only one parameter and it is still of the object type, type conversion is still required, but fortunately, you can have parameters. By combining multiple parameters into a class and passing the instance of this class as parameters, You can implement multiple parameters. Multiple parameters need to be passed. Although the required parameters are packaged into a class, multiple parameters can be transferred by entrusting ParameterizedThreadStart. However, because the input parameter of this delegate is an object, therefore, it is inevitable to perform parameter conversion. There are several common parameter passing methods below. Let's take a look at using special thread classes. This is a classic mode that many programmers love to use, simply put, it is to put the method that requires another thread to execute, and put the required parameters into a class. The parameter is used as the class attribute. When calling, it declares such an instance, and then initializes the attribute, when the method is executed, the initialized attributes in the class are used for execution. In this way, the method itself does not need parameters, but achieves the effect of passing multiple parameters, therefore, we can use the ThreadStart delegate without parameters mentioned at the beginning of this article, and because the methods and parameters to be executed are put in a class, it fully reflects the characteristics of object-oriented. the specific method is as follows: here is an example of the area calculation method. We wrap this method in a class. The input parameter Diameter (Diameter) is a field publ of this class. Ic class MyThread {public double Diameter = 10; public double Result = 0; public MyThread (int Diameter) {this. diameter = Diameter;} public void Calculate () {Console. writeLine ("Calculate Start"); Thread. sleep (2000); Result = Diameter * Math. PI; Console. writeLine ("Calculate End, Diameter is {0}, Result is {1}", this. diameter, Result) ;}} MyThread t = new MyThread (5.0); ThreadStart threadStart = new T HreadStart (t. calculate) Thread thread = new Thread (threadStart); thread. start ();} Example 3: This method changes the parameter transfer to attribute sharing. You can specify the number of variables you want to pass. In encapsulation, it is good to encapsulate the data involved in the logic and logic. This method also has a smart variant that uses the anonymous method, which saves the independent classes, this method is provided now. Double Diameter = 6; double Result = 0; Thread ta = new Thread (new ThreadStart (delegate () {Thread. sleep (2000); Result = Diameter * Math. PI; Console. writeLine ("anonymous Calculate End, Diameter is {0}, Result is {1}", Diameter, Result) ;;}); ta. start (); example 4: This method is the same as the preceding example. It changes the parameter transfer to the call to the variable, thus canceling the parameter transfer. However, the latter makes full use of a property of the anonymous method, that is, it can directly use the local variables of the current context, such as Diameter in the delegate, and Result. of course, the disadvantage of doing so is that if the anonymous method is too long, the readability of the program will be reduced, so few people usually do this. Here we provide this method for your reference. For delegated information, see the smart readers. Since fields can be used to input variables, you can also use fields to output variables, for example, in the above two examples, we can see that the calculation results are written into a variable named Result (highlighted). Can we directly access this variable to get the calculation Result? In this case, there is a fatal problem: Since it is asynchronous execution, how does the main thread know when the split thread completes the calculation? For example, in the above two examples, our threads are sleeping for 2000 milliseconds before calculation. If the main thread accesses the Result before the computation is completed, only one 0 value can be obtained. so we have a series of solutions below. To pass parameters and return parameters, the main Thread needs to know when the sub-Thread is executed. You can use the Thread. threadState enumeration to determine when the thread's ThreadState = ThreadState. when you Stop the job, it usually means that the thread has finished the job and the result is available. If it is not in this status, continue to execute other jobs, or wait for a while and try again. if we need to wait for multiple subthreads to return data and use their results for asynchronous computing, it is called thread synchronization, next, we will introduce another method that I recommend. It can customize the number of parameters and return data, which is also relatively convenient to use, to use the delegate asynchronous call method and callback, we first need to define the method to be asynchronously called as a delegate, and then use BeginInvoke for asynchronous call. The first parameter of BeginInvoke is the diameter, the second is the method called after the thread is executed. Delegate double CalculateMethod (double Diameter); static CalculateMethod calcMethod; double result = 0; static void Main (string [] args) {calcMethod = new CalculateMethod (Calculate); calcMethod. beginInvoke (5, new AsyncCallback (TaskFinished), null );} /// <summary> // The function called by the thread // <summary> public static double Calculate (double Diameter) {return Diameter * Math. PI ;}//< summary> /// the callback function after the thread is completed /// <summary> pu Blic static void TaskFinished (IAsyncResult result) {result = calcMethod. endInvoke (result);} (example 5) Note: In the TaskFinished method executed after the thread execution is complete, we use EndInvoke to obtain the return value of this function. The thread pool thread is a good thing, but it is also a large resource consumer. In many cases, we need to use multiple threads, but we do not want too many threads. This is the role of the thread pool ,. net provides a ready-made thread pool ThreadPool. Its usage is as follows: WaitCallback w = new WaitCallback (Calculate); ThreadPool. queueUserWorkItem (w, 1.0); ThreadPool. queueUserWorkItem (w, 2.0); ThreadPool. queueUserWorkItem (w, 3. 0); ThreadPool. queueUserWorkItem (w, 4.0); public static void Calculate (double Diameter) {return Diameter * Math. PI;} Example 6 first defines a WaitCallback delegate. The WaitCallback format is void WaitCallback (object state), that is, your method must conform to this format, and then calls QueueUserWorkItem, add this task to the thread pool. When the county or city pool has idle lines, your code will be scheduled and run. Every process has a thread pool. The default thread pool size is 25. We can set its maximum value through the SetMaxThreads method. [Note] Since each process has only one thread pool, if the thread pool is used in iis or sqlserver processes and the maximum capacity of the thread pool needs to be set, it will affect the iis process or SQL process, so be careful in both cases. When talking with everyone about control, I found that all colleagues who are used to object-oriented thinking are always troubled by the execution context in the multi-thread scenario. For example, in Example 5, the main program starts the subthread to execute the Calculate method, and calls back TaskFinished after execution. If the main thread id is 1, The subthread id is 2, so Calculate must be executed in the thread id = 2. What about the TaskFinished callback function? It is also executed in the context of the thread whose id is 2. If you do not believe it, try to output the thread id. This is usually not a problem, but when we need to use the subthread in Winform programming, this may cause problems. We will discuss this issue below. Special features of multi-thread programming in form programs when we modify the callback code in Example 5 and move it to winform, we can see the problem. Public static void TaskFinished (IAsyncResult result) {result = calcMethod. endInvoke (result); this. textBox1.Text = result;} the program was originally intended to write a TextBox into the result after the thread was executed. However, when the program runs this. textBox1.Text = result an error is reported here. in the past, WinForm had strict requirements on threads. Apart from the threads used to create these controls, other threads do not allow cross-thread access to the properties and methods of controls on WinForm (except for a few special attributes). In some versions of the system, such as XP, this issue is handled, cross-thread control access can be executed, but most windows systems are not. If we do need to modify the control attributes across threads or call the control method, you must use the control method Invoke. This method can switch the execution context back to the thread for creating these controls. As follows: delegate void changeText (string result); public static void TaskFinished (IAsyncResult result) {result = calcMethod. endInvoke (result); this. beginInvoke (new changeText (this. textBox1.AppendText), t. result. toString ()} Because methods must be used in the delegate, I use the AppendTex method t instead of directly setting the Text attribute. If you want to set the text attribute, you must wrap the method yourself, then connect to the delegate. 2. introduction to multi-threaded instances the question is raised: the thread synchronization problem of a single Write Program/multiple reading programs refers to the Write Program (thread) when any number of threads access shared resources) you need to modify shared resources, while the reading program (thread) needs to read data. In this synchronization problem, it is easy to obtain the following two requirements: 1) when a thread is writing data, other threads cannot write or read data; 2) when a thread is reading data, other threads cannot write but can read data. This problem is often encountered in the database application environment. For example, there are n end users who need to access the same database at the same time. M users need to store data into the database, and n-m users need to read records in the database. Obviously, in this environment, we cannot allow two or more users to update the same record at the same time. If two or more users attempt to modify the same record at the same time, the information in the record will be damaged. We do not allow a user to update database records, while allowing another user to read the records. Because the read record may contain both updated and unupdated information, this record is invalid. Implementation analysis requires that any thread must apply for a lock before writing or reading resources. Read locks and write locks are divided according to different operations. After the operation is completed, the corresponding locks should be released. To change the requirements of a single write program or multiple read programs, you can get the following form: a successful condition for a thread to apply for a read lock is that there is no active write thread currently. A successful condition for a thread to apply for a write lock is that there is currently no active (for the lock) thread. Therefore, a variable m_nActive is introduced to indicate whether there is an active thread and whether it is a writing or reading thread. If m_nActive> 0, it indicates the number of reading threads of the current active activity, if m_nActive = 0, it indicates that there are no active threads, m_nActive <0, indicating that there are currently write threads active. Note that m_nActive <0, only the value of-1 can be obtained, because only one write thread is allowed. To determine the type of the lock owned by the active thread, we use the Thread Local Storage Technology (see other reference books) to associate the thread with the special flag. The prototype of the function used to apply for the read lock is public void AcquireReaderLock (int millisecondsTimeout). The parameter is the waiting time of the thread. The function is defined as follows: public void AcquireReaderLock (int millisecondsTimeout) {// m_mutext can be obtained quickly to enter the critical section m_mutex.WaitOne (); // whether there is a write thread bool bExistingWriter = (m_nActive <0); if (bExistingWriter) {// when the number of waiting read threads increases by 1, when the lock is released, based on the number of scheduling threads m_nWaitingReaders ++;} else {// The current active thread is added with 1 m_nActive ++;} m_mutex.ReleaseMutex (); // The storage lock flag is Reader System. localDataStoreSlot slot = Thread. getNamedDataSlot (m_strThreadSlotName); obje Ct obj = Thread. GetData (slot); LockFlags flag = LockFlags. None; if (obj! = Null) flag = (LockFlags) obj; if (flag = LockFlags. none) {Thread. setData (slot, LockFlags. reader);} else {Thread. setData (slot, (LockFlags) (int) flag | (int) LockFlags. reader);} if (bExistingWriter) {// wait for the specified time this. m_aeReaders.WaitOne (millisecondsTimeout, true);} copy the code into the critical section first (used to ensure the correctness of the number of active threads in a multi-threaded environment) to determine the number of active threads, if a write thread (m_nActive <0) exists, the number of read threads waiting for the specified time increases by 1. If the current active thread is a read thread (m_nActive> = 0), the read thread can continue to run. The prototype of the function for applying for the write lock is public void AcquireWriterLock (int millisecondsTimeout). The parameter is the waiting time. The function is defined as follows: public void AcquireWriterLock (int millisecondsTimeout) {// m_mutext can be obtained quickly to enter the critical section m_mutex.WaitOne (); // whether an active thread exists bool bNoActive = m_nActive = 0; if (! BNoActive) {m_nWaitingWriters ++;} else {m_nActive --;} m_mutex.ReleaseMutex (); // storage thread lock mark System. localDataStoreSlot slot = Thread. getNamedDataSlot ("myReaderWriterLockDataSlot"); object obj = Thread. getData (slot); LockFlags flag = LockFlags. none; if (obj! = Null) flag = (LockFlags) Thread. getData (slot); if (flag = LockFlags. none) {Thread. setData (slot, LockFlags. writer);} else {Thread. setData (slot, (LockFlags) (int) flag | (int) LockFlags. writer);} // if there is an active thread, wait for the specified time if (! BNoActive) this. m_aeWriters.WaitOne (millisecondsTimeout, true);} it first enters the critical section to determine the number of active threads. If there are active threads, whether it is a write thread or a read thread (m_nActive ), the thread adds 1 to the number of write threads waiting for the specified time. Otherwise, the thread has the write permission. The prototype of the function for releasing the read lock is public void ReleaseReaderLock (). The function is defined as follows: public void ReleaseReaderLock () {System. localDataStoreSlot slot = Thread. getNamedDataSlot (m_strThreadSlotName); LockFlags flag = (LockFlags) Thread. getData (slot); if (flag = LockFlags. none) {return;} bool bReader = true; switch (flag) {case LockFlags. none: break; case LockFlags. writer: bReader = false; break;} if (! BReader) return; Thread. setData (slot, LockFlags. none); m_mutex.WaitOne (); AutoResetEvent autoresetevent = null; this. m_nActive --; if (this. m_nActive = 0) {if (this. m_nWaitingReaders> 0) {m_nActive ++; m_nWaitingReaders --; autoresetevent = this. m_aeReaders;} else if (this. m_nWaitingWriters> 0) {m_nWaitingWriters --; m_nActive --; autoresetevent = this. m_aeWriters;} m_mutex.ReleaseM Utex (); if (autoresetevent! = Null) autoresetevent. set ();} When releasing the read lock, first determine whether the current thread has the read lock (through the mark of local thread storage), and then determine whether there is a waiting read thread. If yes, first add 1 to the current active thread, wait for the number of reading threads to be reduced by 1, and then set the event to a signal. If there is no waiting reading thread, check whether there is a waiting writing thread. If so, the number of active threads is reduced by 1 and the number of waiting writing threads is reduced by 1. The process of releasing the write lock is basically the same as that of releasing the read lock. See the source code. Note that when the lock is released in the program, only one reading program will be awakened. This is because the original calendar of the AutoResetEvent can be changed to ManualResetEvent by yourself and multiple reading programs can be awakened at the same time, in this case, m_nActive should be equal to the total number of waiting reading threads. The test program is taken from an example in. Net FrameSDK and is only slightly modified. The test procedure is as follows: using System; using System. threading; using MyThreading; class Resource {myReaderWriterLock rwl = new myReaderWriterLock (); public void Read (Int32 threadNum) {rwl. acquireReaderLock (Timeout. infinite); try {Console. writeLine ("Start Resource reading (Thread = {0})", threadNum); Thread. sleep (1, 250); Console. writeLine ("Stop Resource reading (Thread = {0})", threadNum);} finally {rwl. releaseReader Lock () ;}} public void Write (Int32 threadNum) {rwl. acquireWriterLock (Timeout. infinite); try {Console. writeLine ("Start Resource writing (Thread = {0})", threadNum); Thread. sleep (1, 750); Console. writeLine ("Stop Resource writing (Thread = {0})", threadNum);} finally {rwl. releaseWriterLock () ;}} class App {static Int32 numAsyncOps = 20; static AutoResetEvent asyncOpsAreDone = new AutoResetEvent (fal Se); static Resource res = new Resource (); public static void Main () {for (Int32 threadNum = 0; threadNum <20; threadNum ++) {ThreadPool. queueUserWorkItem (new WaitCallback (UpdateResource), threadNum);} asyncOpsAreDone. waitOne (); Console. writeLine ("All operations have completed. "); Console. readLine ();} // The callback method's signature MUST match that of a System. threading. timerCallback // dele Gate (it takes an Object parameter and returns void) static void UpdateResource (Object state) {Int32 threadNum = (Int32) state; if (threadNum % 2 )! = 0) res. Read (threadNum); else res. Write (threadNum); if (Interlocked. Decrement (ref numAsyncOps) = 0) asyncOpsAreDone. Set ();}}