[C#學習]在多線程中如何調用Winform

來源:互聯網
上載者:User
問題的產生:

我的WinForm程式中有一個用於更新主視窗的背景工作執行緒(worker thread),但文檔中卻提示我不能在多線程中調用這個form(為什嗎?),而事實上我在調用時程式常常會崩掉。請問如何從多線程中調用form中的方法呢?

解答:

每一個從Control類中派生出來的WinForm類(包括Control類)都是依靠底層Windows訊息和一個訊息泵迴圈(message pump loop)來執行的。訊息迴圈都必須有一個相對應的線程,因為發送到一個window的訊息實際上只會被發送到建立該window的線程中去。其結果是,即使提供了同步(synchronization),你也無法從多線程中調用這些處理訊息的方法。大多數plumbing是掩藏起來的,因為WinForm是用代理(delegate)將訊息綁定到事件處理方法中的。WinForm將Windows訊息轉換為一個基於代理的事件,但你還是必須注意,由於最初訊息迴圈的緣故,只有建立該form的線程才能調用其事件處理方法。如果你在你自己的線程中調用這些方法,則它們會在該線程中處理事件,而不是在指定的線程中進行處理。你可以從任何線程中調用任何不屬於訊息處理的方法。

Control類(及其衍生類別)實現了一個定義在System.ComponentModel命名空間下的介面 -- ISynchronizeInvoke,並以此來處理多線程中調用訊息處理方法的問題:public interface ISynchronizeInvoke
{
    object Invoke(Delegate method,object[] args);
    IAsyncResult BeginInvoke(Delegate method, object[] args);
    object EndInvoke(IAsyncResult result);
    bool InvokeRequired {get;}
}

ISynchronizeInvoke提供了一個普通的標準機制用於在其他線程的對象中進行方法調用。例如,如果一個對象實現了ISynchronizeInvoke,那麼線上程T1上的用戶端可以在該對象中調用ISynchronizeInvoke的Invoke()方法。Invoke()方法的實現會阻塞(block)該線程的調用,它將調用打包發送(marshal)到 T2,並在T2中執行調用,再將傳回值發送會T1,然後返回到T1的用戶端。Invoke()方法以一個代理來定位該方法在T2中的調用,並以一個普通的對象數組做為其參數。

調用者還可以檢查InvokeRequired屬性,因為你既可以在同一線程中調用ISynchronizeInvoke也可以將它重新置放(redirect)到其他線程中去。如果InvokeRequired的傳回值是false的話,則調用者可以直接調用該對象的方法。

比如,假設你想要從另一個線程中調用某個form中的Close方法,那麼你可以使用預先定義好的的MethodInvoker代理,並調用Invoke方法:Form form;
/**//* obtain a reference to the form, 
then: */
ISynchronizeInvoke synchronizer;
synchronizer = form;

if(synchronizer.InvokeRequired)
{
    MethodInvoker invoker = new 
    MethodInvoker(form.Close);
    synchronizer.Invoke(invoker,null);
}
else
    form.Close();

ISynchronizeInvoke不僅僅用於WinForm中。例如,一個Calculator類提供了將兩個數字相加的Add()方法,它就是通過ISynchronizeInvoke來實現的。使用者必須確定ISynchronizeInvoke.Invoke()方法的調用是執行在正確的線程中的。

C# 在正確的線程中寫入調用

列表A. Calculator類的Add()方法用於將兩個數字相加。如果使用者直接調用Add()方法,它會在該使用者的線程中執行調用,而使用者可以通過ISynchronizeInvoke.Invoke()將調用寫入正確的線程中。

列表A:public class Calculator : ISynchronizeInvoke
{
  public int Add(int arg1,int arg2)
  { 
    int threadID = Thread.CurrentThread.GetHashCode();
    Trace.WriteLine( "Calculator thread ID is " + threadID.ToString());
    return arg1 + arg2;
  }
  //ISynchronizeInvoke implementation 
  public object Invoke(Delegate method,object[] args)
  {
    public IAsyncResult BeginInvoke(Delegate method,object[] args)
    {
      public object EndInvoke(IAsyncResult result)
      {
        public bool InvokeRequired
        {
        }
      }
      //Client-side code
      public delegate int AddDelegate(int arg1,int arg2);

      int threadID = Thread.CurrentThread.GetHashCode();
      Trace.WriteLine("Client thread ID is " + threadID.ToString());

      Calculator calc;
      /**//* Some code to initialize calc */

      AddDelegate addDelegate = new AddDelegate(calc.Add);

      object[] arr = new object[2];
      arr[0] = 3;
      arr[1] = 4;

      int sum = 0;
      sum = (int) calc.Invoke(addDelegate,arr);
      Debug.Assert(sum ==7);

      /**//* Possible output:
         Calculator thread ID is 29
         Client thread ID is 30 
      */

或許你並不想進行同步調用,因為它被打包發送到另一個線程中去了。你可以通過BeginInvoke()和EndInvoke()方法來實現它。你可以依照通用的.NET非同步編程模式(asynchronous programming model)來使用這些方法:用BeginInvoke()來發送調用,用EndInvoke()來實現等待或用於在完成時進行提示以及收集返回結果。

還值得一提的是ISynchronizeInvoke方法並非安全類型。 類型不符會導致在執行時被拋出異常,而不是編譯錯誤。所以在使用ISynchronizeInvoke時要格外注意,因為編輯器無法檢查出執行錯誤。

實現ISynchronizeInvoke要求你使用一個代理來在後期綁定(late binding)中動態地調用方法。每一種代理類型均提供DynamicInvoke()方法: public object DynamicInvoke(object[]
args);

理論上來說,你必須將一個方法代理放到一個需要提供對象啟動並執行真實的線程中去,並使Invoke() 和BeginInvoke()方法中的代理中調用DynamicInvoke()方法。ISynchronizeInvoke的實現是一個非同一般的編程技巧,本文附帶的源檔案中包含了一個名為Synchronizer的協助類(helper class)和一個測試程式,這個測試程式是用來論證列表A中的Calculator類是如何用Synchronizer類來實現ISynchronizeInvoke的。Synchronizer是ISynchronizeInvoke的一個普通實現,你可以使用它的衍生類別或者將其本身作為一個對象來使用,並將ISynchronizeInvoke實現指派給它。

用來實現Synchronizer的一個重要元素是使用一個名為WorkerThread的嵌套類(nested class)。WorkerThread中有一個工作項目(work item)查詢。WorkItem類中包含方法代理和參數。Invoke()和BeginInvoke()用來將一個工作項目執行個體加入到查詢裡。WorkerThread建立一個.NET worker線程,它負責監測工作項目的查詢任務。查詢到項目之後,worker會讀取它們,然後調用DynamicInvoke()方法。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.