Introduction
For Windows Forms User Interface Programming, if multithreading is not used, the program is straightforward.
However, in practical applications, multithreading is required to ensure UI responsiveness. This makes interface development complicated.
Problems encountered
As you know, Windows forms is not thread-safe. For example, unless you control the message queue
It is not safe to read or write the attribute values of a control on Windows. forms. The point here is that you can only use messages
The queue thread modifies the controls on your windows forms.
Standard Solution
Of course, we have a mechanism to solve this problem. For every Windows Forms control, there is an invokerequired
. If the value of this attribute is false, the current thread is the message queue thread, and you can perform operations on the properties of the control.
Secure read/write. In addition, there is also a method called invokde, which puts a delegate and its parameters together into a control.
Waiting for calling in the message queue.
Because the call to delegate is directly initiated from the message queue, there is no threading related programming content. However
Type programming is boring. Just to set the text of the next text, or enabling/disabling a control, you will
You need to define a separate method and the corresponding delegate.
Example: random string
To illustrate this situation, I wrote a small Windows Forms Program to generate a random string. The following code snippet demonstrates how
Synchronization is performed between the message loop queue and the working thread.
- CharPickrandomchar (StringDigits)
- {
- Thread. Sleep (100 );
- ReturnDigits [random. Next (digits. Length)];
- }
- Delegate VoidSetbooldelegate (BoolParameter );
- VoidSetinputenabled (BoolEnabled)
- {
- If(! Invokerequired)
- {
- Button1.enabled = enabled;
- Comboboxdigits. Enabled = enabled;
- Numericupdowndigits. Enabled = enabled;
- }
- Else
- Invoke (NewSetbooldelegate (setinputenabled ),New Object[] {Enabled });
- }
- Delegate VoidSetstringdelegate (StringParameter );
- VoidSetstatus (StringStatus ){
- If(! Invokerequired)
- Labelstatus. Text = status;
- Else
- Invoke (NewSetstringdelegate (setstatus ),New Object[] {Status });
- }
- VoidSetresult (StringResult ){
- If(! Invokerequired)
- Textboxresult. Text = result;
- Else
- Invoke (NewSetstringdelegate (setresult ),New Object[] {Result });
- }
- Delegate IntGetintdelegate ();
- IntGetnumberofdigits ()
- {
- If(! Invokerequired)
- Return(Int) Numericupdowndigits. value;
- Else
- Return(Int) Invoke (NewGetintdelegate (getnumberofdigits ),Null);
- }
- Delegate StringGetstringdelegate ();
- StringGetdigits ()
- {
- If(! Invokerequired)
- ReturnComboboxdigits. text;
- Else
- Return(String) Invoke (NewGetstringdelegate (getdigits ),Null);
- }
- VoidWork ()
- {
- Try
- {
- Setinputenabled (False);
- Setstatus ("working ");
- IntN = getnumberofdigits ();
- StringDigits = getdigits ();
- Stringbuilder text =NewStringbuilder ();
- For(IntI = 0; I! = N; I ++)
- {
- Text. append (pickrandomchar (digits ));
- Setresult (text. tostring ());
- }
- Setstatus ("ready ");
- }
- Catch(Threadabortexception)
- {
- Setresult ("");
- Setstatus ("error ");
- }
- Finally
- {
- Setinputenabled (True);
- }
- }
- VoidStart ()
- {
- Stop ();
- Thread =NewThread (NewThreadstart (work ));
- Thread. Start ();
- }
- VoidStop ()
- {
- If(Thread! =Null)
- {
- Thread. Abort ();
- Thread =Null;
- }
- }
I used thread. Abort because it is just a simple example. If you are performing an operation that cannot be interrupted under any circumstances,
Use a flag to notify your thread.
The above code contains a lot of simple and repetitive code. For example, before calling invoke, you must always check invokerequired. Because
If you call invoke before creating a message queue, an error occurs.
Generated thread security wrappers
In the previous article, I introduced how to automatically generate a wrappers class to "implicitly" implement an interface. Extension of the same Code,
You can create wrppers and then automatically let the thread call the correct method.
I will explain how the entire mechanism works later. First, let's take a look at how it works.
First, you can publish relevant attributes without worrying about thread programming. This is what you plan to do even if you do not use multithreading.
- Public BoolInputenabled
- {
- Set
- {
- Button1.enabled = value;
- Comboboxdigits. Enabled = value;
- Numericupdowndigits. Enabled = value;
- }
- }
- Public StringStatus
- {
- Set{Labelstatus. Text = value ;}
- }
- Public IntNumberofdigits
- {
- Get{ReturnNumericupdowndigits. value ;}
- }
- Public StringDigits
- {
- Get{ReturnComboboxdigits. Text ;}
- }
- Public StringResult
- {
- Set{Textboxresult. Text = value ;}
- }
Then, you define an interface that contains all the attributes or methods you intend to call from another thread.
- InterfaceIformstate
- {
- IntNumberofdigits {Get;}
- StringDigits {Get;}
- StringStatus {Set;}
- StringResult {Set;}
- BoolInputenabled {Set;}
- }
Now, in the working thread, all you have to do is create a thread-safe wrapper and use it. You no longer need to input repeated code.
- VoidWork ()
- {
- Iformstate state = wrapper. Create (Typeof(Iformstate ),This);
- Try
- {
- State. inputenabled =False;
- State. Status = "working ";
- IntN = state. numberofdigits;
- StringDigits = state. digits;
- Stringbuilder text =NewStringbuilder ();
- For(IntI = 0; I <n; I ++)
- {
- Text. append (pickrandomchar (digits ));
- State. Result = text. tostring ();
- }
- State. Status = "ready ";
- }
- Catch(Threadabortexception)
- {
- State. Status = "error ";
- State. Result = "";
- }
- Finally
- {
- State. inputenabled =True;
- }
- }
Working mechanism
The wrapper generator uses system. reflection. emit to generate a proxy class. This proxy class contains all the methods required by the interface, and it also contains access attributes.
Each method has a specific signature ).
In the method body, if the return value of invokerequired is false, the original method is called directly. This check is very important, in order
When attached is sent to a message thread, the call can also work.
If invokerequired returns true, a delegate pointing to the original method is passed as a parameter that calls the form's invode method. Delegate
In this way, the delegate type is not created repeatedly for methods with the same signature.
Since the wrapper generator uses the isynchronizeinvoke interface for Synchronous calling, you can use it in non-Windows-forms programs. What you want to do
It is just the implementation interface and probably its own implementation of something similar to message queue.
Limitations and warnings
One important thing to understand is that thread-safe wrapper is used to hide thread synchronization, but it does not mean that thread synchronization is not performed.
Therefore, if a thread-safe wrapper is used to access an attribute, it is much slower to directly access this attribute when invokerequired returns true. Therefore,
If you make complex changes to your form from several different threads, it is best to put them in a method to complete it once instead of calling it several times separately.
Another thing to keep in mind is that not all types are safe to pass between threads without synchronization. Generally, only int, datetime, and
Some immutable reference types, such as string, are safe. If you want to pass a mutable reference type from one thread to another, for example
Stringbuilder, so you must be careful. If this object is not modified in different threads, or it is thread-safe, the transfer is OK. If
If you have any questions, make a deep copy transfer. Do not use references.