多線程之旅七——GUI線程模型,消息的投遞(post)與處理


基於消息的GUI構架

在過去的日子中,大部分編程語言平台的GUI構架幾乎沒有發生變化。雖然在細節上存在一些差異,比如在功能和編程風格上,但大部分都是采用了相同的構架來響應用戶輸入以及重新繪制屏幕。這種構架可以被總結為“單線程且基於消息”。

 

 
Message msg;

While(GetMessage(msg))
{
    TranslateMessage(msg);
    DispatchMessage(msg);
}

 

 

 

這段代碼可以稱為消息循環。在這個循環中,執行順序是串行的,一個GetMessage只能在前一個GetMessage執行完以后才能執行。

拿WPF或WindowsForm舉例,每個線程至少會創建一個擁有消息列隊的窗口,並且這個線程的任務之一就是處理列隊中的各個消息。只要在應用程序中調用了Application.Run,那么執行這個方法的線程就默認被賦予了這個任務。隨后的所有GUI事件,例如用戶引發的事件(點擊按鈕,關閉窗口等),系統引發的事件(重繪窗口,調整大小等),以及應用程序中自定義組件的特定事件等,都將把相應的消息投遞給這個消息列隊來實現。這意味着,在調用了run之后,隨后發生的大部分工作都是由事件處理器為了響應GUI事件而生成的。

 

如圖:

消息循環

 

 

GUI線程

Gui線程負責取走(get)和分發(dispatch)消息,同時負責描繪界面,如果GUI線程阻塞在分發處理消息這一步的話,那么消息就會在消息隊列中積累起來,並等待GUI線程回到消息列隊來。

如果阻塞的是一個長時間的操作,比如下載一個文件的話,假設10秒鍾,那么用戶在10秒鍾內都不能進行任何操作,因為線程沒法獲取新的消息進行處理。

這就是為什么在Windows中存在着MsgWaitForMultipleObjects的原因,這個API使得線程在等待的同時仍然可以運行消息循環。在.NET中,你連這個選擇都沒有。

消息分發時要考慮到復雜的重入性問題,很難確保一個事件處理器阻塞時,可以安全分發其他GUI事件以響應消息。

因此,一種相對而言更容易掌握的解決方法就是只有在GUI線程中的代碼才能夠操縱GUI控件,在更新GUI時所需要的其他數據和計算都必須在其他線程中完成,而不是在GUI線程上。如圖:

 

其他線程

 

通常這意味着把工作轉交給線程池完成,然后在得到結果后把結果合並回GUI線程上。這也就是我們接下來要介紹的兩個類。

 

SynchronizationContext 和 BackgroundWorker

SynchronizationContext 對不同線程間的調度操作進行同步,把一些異步操作的結果Post回GUI線程里。

 

WPF中DispatcherSynchronizationContext的實現

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);
} 

 

有些情況下,如在控制台中我們不能通過SynchronizationContext類的Current屬性獲取SynchronizationContext實例,我們包裝了一下這個方法。

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);
}

 

 

這個方法將一個普通的AsyncCallback方法轉換成特殊的AsyncCallback 方法,它通過SynchronizationContext 來調用。這樣無論線程模型中是否含有GUI線程,都可以正確的調用。

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;
      }
   }
}

這其實就是AsyncOperationManager的基本原理。

public  static  class  AsyncOperationManager 
{ 
    public  static  SynchronizationContext  {  getj  setj  } 
    public  static  AsyncOperation  CreateOperation( object  userSuppliedState ) ; 
}

 

BackGroundWorker是在前面所說的基礎上構建起來的更高層次的抽象,它對GUI程序中一些最常用的操作給出了規范的定義。有三個事件:

DoWork 、ProgressChanged 和 RunWorkerCompleted

在程序中調用RunWorkerAsync方法則會啟動DoWork事件的事件處理,當在事件處理過程中,調用 ReportProgress方法則會啟動ProgressChanged事件的事件處理,而當DoWork事件處理完成時,則會觸發 RunWorkerCompleted事件。

    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!";
            }
        }
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM