Document directory
- Win32
- . NET 1.0 Windows Forms
- . NET 2.0 Windows Forms
- . Net 3.0 WPF
- Conculsion
Both Win32, Windows form, WPF, and swing do not support direct access to APIS by threads other than gui threads. Let's review this development process today. How can a common operation be encapsulated and then encapsulated.
Win32
In the Windows SDK era, we all know that the interface is a big wndproc control.
switch (message)
{
case WM_PAINT:
case WM_DESTROY:
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
If we need another thread to perform some time-consuming Io operations and call back the updated interface, we need to define a callback queue and then process it in wm_idle, you can either create a message type by using the message queue of the window itself:
// at startup
int OUR_APP_IO_COMPLETED = RegisterWindowMessage("OUR_APP_IO_COMPLETED");
----
// after io completed
PostMessage(hwnd, OUR_APP_IO_COMPLETED, xxx, xxx);
In this way, we can complete the interface update operation in the GUI thread.
. NET 1.0 Windows Forms
The appearance of Windows Forms makes many things much easier. Cross-thread GUI operations are also much simpler. Because we have delegate and control. invoke. The former is an upgraded version of the function pointer, and control. Invoke is an upgraded version of postmessage:
public delegate void IOCompletedHandler();
public void OnIOCompleted() {
control.Invoke(new IOCompletedHandler(HandleIOCompleted));
}
public void HandleIOCompleted() {
}
A threadcallbacklist is maintained in the internal control. Calling invoke is equivalent
1. encapsulate delegate and parameters together and press them into threadcallbacklist.
2. Ensure that threadcallbackmessage is registered (registerwindowmessage)
3. postmessage: triggers callback.
4. waitforhandle: Wait for the GUI thread to complete the processing (this step can be omitted if begininvoke is used)
In Windows Forms wndproc, a case statement must be added to respond to postmessage. When you get the message, go to threadcallbacklist to retrieve the delegate to be called, and then call it one by one.
Control. Invoke makes our life much easier. However, it still cannot avoid the so-called begininvoke Dance (begininvoke dance ):
//http://ikriv.com:8765/en/prog/info/dotnet/MysteriousHang.html
delegate void MyHandlerDelegate();
void MyHandler()
{
// "The BeginInvoke dance"
if (this.InvokeRequired) // assuming this descends from Control
{
BeginInvoke( new MyHandlerDelegate(MyHandler) );
return;
}
// assume we are on the main GUI thread
do GUI stuff
}
The internal implementation of invokerequired is to safely compare the thread IDs of the current thread and GUI thread.
In addition, Control. Invoke also requires us to ensure that the corresponding control handle has been created. To avoid this problem, winform has an ingress Control for us to use.
Application.ThreadContext.FromCurrent().MarshingControl;
. NET 2.0 Windows Forms
If I provide a callback interface. I usually call the callback method directly. If I know that the callback method must be executed in the GUI thread, Control. Invoke must be used to call the callback method. Therefore, in the absence of information, the provider of the callback interface cannot guarantee the environment of the execution thread of the callback method. Therefore, according to the strategy mode, we can define a callbackstrategy
public interface CallbackExecutor {
void Execute(Delegate callback, object[] args);
}
public class DefaultCallbackExecutor : CallbackExecutor {
public void Execute(Delegate callback, object[] args) {
callback.Invoke(args);
}
}
public class WindowsFormsCallbackExecutor : CallbackExecutor {
private Control marshalingControl;
public WindowsFormsCallbackExecutor() {
Application.ThreadContext context = Application.ThreadContext.FromCurrent();
if (context != null)
{
this.marshalingControl= context.MarshalingControl;
}
}
public void Execute(Delegate callback, object[] args) {
marshalingControl.Invoke(callback, args);
}
}
Then, the provider of the callback method does not need to be embarrassed. It only needs to call callbackexecutor, And the callback method can also do its own work with peace of mind. Thus, no one needs to do begininvoke dance.
This is the basic principle of synchronizationcontext introduced by. net. Because Microsoft is not an API enthusiast, it eliminates the need for a common interface, allowing windowsformsynchronizationcontext to inherit from the synchronizationcontext class. Synchronizationcontext is unique in the thread. To obtain the sync context of the current thread, you can use:
SynchronizationContext.Current.Post(delegate{//your code}, null);
Asyncoperationmanager and asyncoperation are introduced together with the synchronizationcontext class. It is also the encapsulation of synchronizationcontext. Generally, asyncoperationmanager is recommended. Asyncoperationmanager obtains the sync context of the current thread when creating asyncoperation and stores it in syncoperation. So when asyncoperation is used for back-and-forth calling, it will use the context of the thread in which it was created at the beginning.
// initialize in GUI thread
asyncOperation = AsyncOperationManager.Create(null);
// when you want to call back
asyncOperation.Post(delegate{//your code}, null);
For classes like backgroundworker, the secret of callback's direct access to the GUI is that callback is not called directly, but has
Asyncoperation. As long as the asyncoperation is created in the GUI thread, all the callbacks on behalf of it will go through
Marshalingcontrol is used to control. invoke.
. Net 3.0 WPF
Control. Invoke is changed to dispatcher. invoke. The internal implementation is the same. There is a callback queue and then postmessage. Dispatchersynchronizationcontext is also introduced.
Conculsion
From Direct postmessage to Control. invoke to synchronizationcontext to asyncoperationmanager to backgroundworker, the responsibility is ultimately shirked to the callback provider. However, due to inconsistent introduced time, many callback providers do not use asyncoperation. At this time, we need to package ourselves, for example:
Code
using System;
using System.ComponentModel;
using System.Net;
using log4net;
namespace xxx
{
public class WebClientFileUploader : FileUploaderListeners, FileUploader
{
private static readonly ILog LOGGER = LogManager.GetLogger(typeof (WebClientFileUploader));
private readonly WebClient webClient;
private readonly Uri address;
private readonly AsyncOperation asyncOperation;
public WebClientFileUploader(Uri address)
{
asyncOperation = AsyncOperationManager.CreateOperation(null);
webClient = new WebClient();
webClient.UploadFileCompleted += HandleUploadFileCompleted;
this.address = address;
}
private void HandleUploadFileCompleted(object sender, UploadFileCompletedEventArgs eventArgs)
{
try
{
var result = eventArgs.Result;
LOGGER.Info("Request completed.");
asyncOperation.Post(state => HandleCompleted(result), null);
}
catch (Exception exception)
{
LOGGER.Error("Request failed.", exception);
asyncOperation.Post(state => HandleError(exception), null);
}
}
public void Cancel()
{
LOGGER.Info("Cancelling the request.");
webClient.CancelAsync();
}
public void Upload(string fileName)
{
LOGGER.Info("Starting to upload to " + address + " with file " + fileName + " using real web client.");
webClient.UploadFileAsync(address, WebRequestMethods.Http.Post, fileName);
}
}
}
In this way, we do not need to use control. Invoke on the construction site, and we should not directly use control. invoke.