Message-based GUI Architecture
In the past few days, the GUI architecture of most programming language platforms has hardly changed. Although there are some differences in details, such as functional and programming style, most of them adopt the same architecture to respond to user input and redraw the screen. This architecture can be summarized as "Single-threaded and message-based ".
Message msg;While(GetMessage(msg)){ TranslateMessage(msg); DispatchMessage(msg);}
This code can be called a message loop. In this loop, the execution sequence is sequential. A GetMessage can only be executed after the previous GetMessage is executed.
Take WPF or WindowsForm as an example. Each thread creates at least one window with a message queue, and one of the tasks of this thread is to process various messages in the queue. As long as Application. Run is called in the Application, the thread that executes this method is assigned this task by default. All subsequent GUI events, such as user-triggered events (click the button, close the window, etc.), system-triggered events (re-painting the window, resize, etc ), as well as specific events of custom components in the application, the corresponding messages will be delivered to this message queue for implementation. This means that after the run is called, most of the subsequent work is generated by the event processor in response to GUI events.
GUI thread
The Gui thread is responsible for retrieving (get) and dispatching (dispatch) messages, and also for describing the interface. If the GUI thread is blocked in the delivery and processing of messages, then the message will accumulate in the message queue and wait for the GUI thread to return to the message queue.
If the block is a long operation, such as downloading an object for 10 seconds, the user cannot perform any operation within 10 seconds, because the thread cannot obtain new messages for processing.
This is why MsgWaitForMultipleObjects exists in Windows. This API allows the thread to still run the message loop while waiting. In. NET, you don't even have this option.
Complex re-entry issues must be taken into account during message delivery. It is difficult to ensure that other GUI events can be securely distributed to respond to messages when an event processor is congested.
Therefore, a relatively easy solution is that only the code in the GUI thread can manipulate the GUI control, other data and computation required when updating the GUI must be completed in other threads, rather than on the GUI thread.
This usually means that the work is transferred to the thread pool for completion, and then the results are merged back to the GUI thread after the results are obtained. This is the two classes we will introduce next.
SynchronizationContext and BackgroundWorker
SynchronizationContext synchronizes scheduling operations between different threads, and posts the results of some asynchronous operations back to the GUI thread.
Implementation of DispatcherSynchronizationContext in WPF
public override void Post(SendOrPostCallback d, object state) { _dispatcher.Beginlnvoke(DispatcherPriority.Normal, d, state); } public override void Send(SendOrPostCallback d, object state) { _dispatcher.lnvoke(DispatcherPriority.Normal, d, state);}
In some cases, for example, in the console, we cannot get the SynchronizationContext instance through the Current attribute of the SynchronizationContext class. We have wrapped this method.
private static AsyncCallback SyncContextCallback(AsyncCallback callback) { // Capture the calling thread's SynchronizationContext-derived object SynchronizationContext sc = SynchronizationContext.Current; // If there is no SC, just return what was passed in if (sc == null) return callback; // Return a delegate that, when invoked, posts to the captured SC a method that // calls the original AsyncCallback passing it the IAsyncResult argument return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);}
This method converts a common AsyncCallback method to a special AsyncCallback method, which is called through SynchronizationContext. In this way, no matter whether the thread model contains a GUI thread or not, it can be called correctly.
internal sealed class MyWindowsForm : Form { public MyWindowsForm() { Text = "Click in the window to start a Web request"; Width = 400; Height = 100; } protected override void OnMouseClick(MouseEventArgs e) { // The GUI thread initiates the asynchronous Web request Text = "Web request initiated"; var webRequest = WebRequest.Create("http://Wintellect.com/"); webRequest.BeginGetResponse(SyncContextCallback(ProcessWebResponse), webRequest); base.OnMouseClick(e); } private void ProcessWebResponse(IAsyncResult result) { // If we get here, this must be the GUI thread, it's OK to update the UI var webRequest = (WebRequest)result.AsyncState; using (var webResponse = webRequest.EndGetResponse(result)) { Text = "Content length: " + webResponse.ContentLength; } }}
This is actually the basic principle of AsyncOperationManager.
public static class AsyncOperationManager { public static SynchronizationContext { getj setj } public static AsyncOperation CreateOperation( object userSuppliedState ) ; }
BackGroundWorker is a high-level abstraction built on the basis of the above mentioned. It provides a standard definition for some of the most common operations in GUI programs. There are three events:
DoWork, ProgressChanged, and RunWorkerCompleted
When the RunWorkerAsync method is called in a program, the DoWork event processing is started. When the ReportProgress method is called during event processing, the event processing of the ProgressChanged event is started, when the DoWork event processing is complete, the RunWorkerCompleted event is triggered.
public partial class Form1 : Form { public Form1() { InitializeComponent(); backgroundWorker1.WorkerReportsProgress = true; backgroundWorker1.WorkerSupportsCancellation = true; } private void startAsyncButton_Click(object sender, EventArgs e) { if (backgroundWorker1.IsBusy != true) { // Start the asynchronous operation. backgroundWorker1.RunWorkerAsync(); } } private void cancelAsyncButton_Click(object sender, EventArgs e) { if (backgroundWorker1.WorkerSupportsCancellation == true) { // Cancel the asynchronous operation. backgroundWorker1.CancelAsync(); } } // This event handler is where the time-consuming work is done. private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; for (int i = 1; i <= 10; i++) { if (worker.CancellationPending == true) { e.Cancel = true; break; } else { // Perform a time consuming operation and report progress. System.Threading.Thread.Sleep(500); worker.ReportProgress(i * 10); } } } // This event handler updates the progress. private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { resultLabel.Text = (e.ProgressPercentage.ToString() + "%"); } // This event handler deals with the results of the background operation. private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled == true) { resultLabel.Text = "Canceled!"; } else if (e.Error != null) { resultLabel.Text = "Error: " + e.Error.Message; } else { resultLabel.Text = "Done!"; } } }