Preface
During our application development process, we often encounter some problems and need to use multi-threaded technology to solve them. This article explains some main problems related to multithreading through several sample programs.
CEO task operations
Many types of applications require long-term operations, such as executing a print task and requesting a web service call. In this case, the user usually transfers other tasks to wait for the task to be completed, and wants to monitor the progress of the task at any time.
? /P>
The following code snippet shows how the user interface is updated when a long task is executed.
// Display the progress bar
Void showprogress (INT totalstep, int currentstep)
{
_ Progress. Maximum = totalstep;
_ Progress. value = currentstep;
}
// Execute the task
Void runtask (INT seconds)
{
// Display progress every 1/4 seconds
For (INT I = 0; I <seconds * 4; I ++)
{
Thread. Sleep (250 );
// Display the progress bar
Showprogress (seconds * 4, I + 1 );
}
}
Private void _ btnrun_click (Object sender, system. eventargs E)
{
Runtask (convert. toint32 (_ txtsecond. Value ));
}
When we run the above program, there is no problem throughout the long task process. So there is no problem? When we switch the application to do other things and then switch back, the problem will happen! The main form will see the following situation:
This problem will certainly happen because our current application is single-threaded. Therefore, when the thread executes a task, it cannot re-draw the user interface at the same time.
Why does the problem occur after we switch the application? This is because when you switch the current application to the background and switch back to the foreground, we need to re-draw the entire user interface. However, the application is executing the task and there is no time to process the re-painting of the user interface.
How to solve the problem? We need to run long tasks in the background to free up the user interface threads, so we need another thread.
Asynchronous thread operation
The click processing of the buttons in the above program is as follows:
Private void _ btnrun_click (Object sender, system. eventargs E)
{
Runtask (convert. toint32 (_ txtsecond. Value ));
}
Recall the cause of the above problem until the runtask is returned after execution, and the click processing function cannot be returned. This means that the user interface cannot process the re-painting event or any other event. One solution is to create another thread. The code snippet is as follows:
Using system. Threading;
Private int _ seconds;
// The worker thread of the execution task enters the vertex
Void runtaskthreadstart ()
{
Runtask (_ seconds );
}
// Eliminate the blocking problem of the user interface thread by creating a working thread
Private void _ btnrun_click (Object sender, system. eventargs E)
{
_ Seconds = convert. toint32 (_ txtsecond. value );
Thread runtaskthread = new thread (New threadstart (runtaskthreadstart ));
Runtaskthread. Start ();
}
Now, we no longer need to wait until runtask execution is complete before returning from the click event. We have created a new working thread and started it to work and run.
Runtaskthread. Start (); schedules and immediately returns the newly created worker thread, allowing the user interface thread to regain control and execute its own work. Now, if the user switches the application again, because the worker thread executes the task in his own space, the user interface thread is freed to process various events including user interface re-painting, the problem we encountered above is solved.
Delegated asynchronous call
In the code above, we noticed that we didn't pass any parameters to the worker thread entry point (runtaskthreadstart). We passed parameters to the worker thread by declaring the _ seconds field of a form class. In an application, it is also very painful to directly pass parameters to the worker thread.
How can we improve it? We can use Delegation for asynchronous calls. Delegation supports parameter passing. In this way, the problem we just encountered is eliminated, so that we can eliminate additional field declarations and additional working thread functions.
If you are not familiar with delegation, you can simply think of it as a safe function pointer. Use a delegated asynchronous call. The code snippet is as follows:
// Task execution delegate statement
Delegate void runtaskdelegate (INT seconds );
// Solve the parameter passing problem by creating a delegate
Private void _ btnrun_click (Object sender, system. eventargs E)
{
Runtaskdelegate runtask = new runtaskdelegate (runtask );
// Delegate synchronous call Method
Runtask (convert. toint16 (_ txtsecond. Value ));
}
// Solve the problem of passing parameters by creating a delegate and eliminate the thread blocking problem on the user interface through the asynchronous call of the Delegate
Private void _ btnrun_click (Object sender, system. eventargs E)
{
Runtaskdelegate runtask = new runtaskdelegate (runtask );
// Delegate asynchronous call Method
Runtask. begininvoke (convert. toint16 (_ txtsecond. value), null, null );
}
Multithreading Security
So far, we have solved the problem of long tasks and the trouble of passing parameters. But have we solved all the problems? The answer is no.
We know that there is a mandatory principle in Windows programming, that is, the form cannot be operated in any thread other than the creation thread of a form.
The preceding program has the following problem: the worker thread modifies the properties of the progress bar on the user interface in the showprogress method. Why is the program running normal without any problem?
The problem does not occur because the current Windows XP operating system has a very robust solution to this type of problem, which allows us to avoid the problem. However, our current program cannot ensure that it can run normally in other operating systems!
The real solution is to recognize the problem and avoid it in the program.
How can we avoid the security problem of multi-threaded form resource access? In fact, it is very simple. There are two methods:
One way is to delegate access to user interface resources regardless of whether the thread is a user interface thread;
Another method is to have an invokerequired attribute in each Windows Forms user interface class, which is used to identify whether the current thread can directly access form resources. You only need to check the value of this attribute. You can access the corresponding resources only when you allow direct access to the form resources. Otherwise, you need to access the resources through delegation.
The code snippet using the first secure method is as follows:
// Display the delegate statement of the progress bar
Delegate void showprogressdelegate (INT totalstep, int currentstep );
// Display the progress bar
Void showprogress (INT totalstep, int currentstep)
{
_ Progress. Maximum = totalstep;
_ Progress. value = currentstep;
}
// Task execution delegate statement
Delegate void runtaskdelegate (INT seconds );
// Execute the task
Void runtask (INT seconds)
{
Showprogressdelegate showprogress = new showprogressdelegate (showprogress );
// Display progress every 1/4 seconds
For (INT I = 0; I <seconds * 4; I ++)
{
Thread. Sleep (250 );
// Display the progress bar
This. Invoke (showprogress, new object [] {seconds * 4, I + 1 });
}
}
The code snippet using the second security method is as follows:
// Display the delegate statement of the progress bar
Delegate void showprogressdelegate (INT totalstep, int currentstep );
// Display the progress bar
Void showprogress (INT totalstep, int currentstep)
{
If (_ progress. invokerequired)
{
Showprogressdelegate showprogress = new showprogressdelegate (showprogress );
// In order to prevent the worker thread from being blocked, use Asynchronous call delegate
This. begininvoke (showprogress, new object [] {totalstep, currentstep });
}
Else
{
_ Progress. Maximum = totalstep;
_ Progress. value = currentstep;
}
}
// Task execution delegate statement
Delegate void runtaskdelegate (INT seconds );
// Execute the task
Void runtask (INT seconds)
{
// Display progress every 1/4 seconds
For (INT I = 0; I <seconds * 4; I ++)
{
Thread. Sleep (250 );
// Display the progress bar
Showprogress (seconds * 4, I + 1 );
}
}
At this point, we have used several examples to illustrate how to execute a task, how to display the task progress asynchronously through multithreading, and solve the security issues of multithreading. I hope that I can help you understand multi-threaded programming, delegate usage, asynchronous calling, and further communicate with you.