使Windows Forms成為安全執行緒的)

來源:互聯網
上載者:User
介紹

對於windows forms使用者介面編程來說,如果不使用多線程的話,程式都是直接了當的。
但是在實際應用中,為了確保UI的響應性,就必須使用多線程。這就導致了介面開發變得複雜起來。

遇到的問題

如大家所知,windows forms並不是安全執行緒的。例如,除非你對訊息佇列進行了控制,那麼對
Windows.Forms上的一個控制項的屬性值進行讀寫並不是安全的。這裡的重點是,你只能通過訊息
隊列線程對你的Windows Forms上的控制項進行修改。

標準解決方案

當然,我們有一個機制來解決這個問題。對於每一個Windows Forms的控制項,都有一個 InvokeRequired
的屬性。如果這個屬性的值是False,那麼當前的線程就是訊息佇列線程,你就可以對控制項的屬性進行
安全的讀寫。另外,還有一個方法 Invokde ,這個方法把一個delegate和他的參數一起放到某個控制項
訊息佇列裡面等待調用。
因為對delegate的調用是直接從訊息佇列裡面發起的,所以沒有什麼threading相關的編程內容。但是這種
類型的編程是及其乏味的。僅僅為了設定一下一個Text的文本,或者enabling/disabling一個控制項,你就
需要定義一個分離的方法和對應的delegate.

例子:隨機字串

為了說明這個情況,我寫了一個小的Windows Forms程式來產生一個隨機的字串。下面的程式碼片段示範了如何
在訊息迴圈隊列和背景工作執行緒之間進行同步。

  1. char PickRandomChar(string digits) 
  2.     Thread.Sleep(100); 
  3.     return digits[random.Next(digits.Length)]; 
  4. delegate void SetBoolDelegate(bool parameter); 
  5. void SetInputEnabled(bool enabled)
  6. {
  7.     if(!InvokeRequired)
  8.     {
  9.         button1.Enabled=enabled; 
  10.         comboBoxDigits.Enabled=enabled; 
  11.         numericUpDownDigits.Enabled=enabled;
  12.     } 
  13.     else 
  14.         Invoke(new SetBoolDelegate(SetInputEnabled),new object[] {enabled}); 
  15. delegate void SetStringDelegate(string parameter);
  16. void SetStatus(string status) { 
  17.     if(!InvokeRequired)
  18.         labelStatus.Text=status; 
  19.     else 
  20.         Invoke(new SetStringDelegate(SetStatus),new object[] {status});
  21. void SetResult(string result) {
  22.     if(!InvokeRequired)
  23.         textBoxResult.Text=result; 
  24.     else
  25.         Invoke(new SetStringDelegate(SetResult),new object[] {result});
  26. delegate int GetIntDelegate(); 
  27. int GetNumberOfDigits()
  28. {
  29.     if(!InvokeRequired) 
  30.         return (int)numericUpDownDigits.Value;
  31.     else 
  32.         return (int)Invoke(new GetIntDelegate(GetNumberOfDigits),null);
  33. delegate string GetStringDelegate(); 
  34. string GetDigits()
  35. {
  36.     if(!InvokeRequired) 
  37.         return comboBoxDigits.Text; 
  38.     else
  39.         return (string)Invoke(new GetStringDelegate(GetDigits),null);
  40. }
  41. void Work() 
  42. {
  43.     try 
  44.     {
  45.         SetInputEnabled(false);
  46.         SetStatus("Working");        
  47.         int n=GetNumberOfDigits();
  48.         string digits=GetDigits();
  49.         StringBuilder text=new StringBuilder();
  50.         for(int i=0;i!=n;i++)
  51.         {
  52.             text.Append(PickRandomChar(digits));
  53.             SetResult(text.ToString());
  54.         }
  55.         SetStatus("Ready");
  56.     }
  57.     catch(ThreadAbortException) 
  58.     {
  59.         SetResult("");
  60.         SetStatus("Error");
  61.     }
  62.     finally 
  63.     {
  64.         SetInputEnabled(true);
  65.     }
  66. }
  67. void Start() 
  68. {
  69.     Stop();
  70.     thread=new Thread(new ThreadStart(Work));
  71.     thread.Start();
  72. }
  73. void Stop() 
  74. {
  75.     if(thread!=null
  76.     {
  77.         thread.Abort();
  78.         thread=null;
  79.     }
  80. }

我使用了 Thread.Abort ,因為這隻是一個簡單的樣本。如果你正在執行一個在任何情況下都不能打斷的操作,
那麼使用一個flag來通知你的線程。
上面的代碼裡面有許多簡單而重複的代碼。比如在調用Invoke之前,你必要總要檢查一下InvokeRequired.因為
如果訊息佇列還沒有建立你就調用了Invoke,就會產生一個錯誤。

產生的安全執行緒wrappers

在之前的文章裡面,我介紹了如何自動產生一個類的wrappers來"隱含"的實現一個介面。同樣的代碼進行擴充,
可以實現建立wrppers,然後自動的讓線程來調用正確的方法。

稍後我將解釋整個機制的工作原理,首先,我們看看怎麼使用。

首先你公布出來相關的屬性,不用關心線程編程相關的事情。這個事情即便你不使用多線程,也是打算要做的。

  1. public bool InputEnabled
  2. {
  3.     set
  4.     { 
  5.         button1.Enabled=value; 
  6.         comboBoxDigits.Enabled=value; 
  7.         numericUpDownDigits.Enabled=value;
  8.     } 
  9. }
  10. public string Status 
  11. {
  12.     set { labelStatus.Text=value;} 
  13. }
  14. public int NumberOfDigits
  15. {
  16.     get { return numericUpDownDigits.Value; }
  17. }
  18. public string Digits 
  19. {
  20.     get { return comboBoxDigits.Text; }
  21. }
  22. public string Result 
  23. {
  24.     set { textBoxResult.Text=value; }
  25. }

然後,你定義一個介面,包含所有的你打算從另外一個線程裡面調用的屬性或者方法。

  1. interface IFormState
  2. {
  3.     int NumberOfDigits { get; } 
  4.     string Digits { get; }
  5.     string Status { set; }
  6.     string Result { set; }
  7.     bool InputEnabled { set; }
  8. }

現在,在背景工作執行緒裡面,你所要做的就是建立一個安全執行緒的wrapper然後使用,那些重複的代碼你再也不需要輸入了。

  1. void Work() 
  2. {
  3.     IFormState state=Wrapper.Create(typeof(IFormState),this);
  4.     try 
  5.     {
  6.         state.InputEnabled=false;
  7.         state.Status="Working";
  8.         int n=state.NumberOfDigits;
  9.         string digits=state.Digits;
  10.         StringBuilder text=new StringBuilder();
  11.         for(int i=0;i<n;i++) 
  12.         {
  13.             text.Append(PickRandomChar(digits));
  14.             state.Result=text.ToString();
  15.         }   
  16.         state.Status="Ready";
  17.     }
  18.     catch(ThreadAbortException) 
  19.     {
  20.         state.Status="Error";
  21.         state.Result=""; 
  22.     }
  23.     finally
  24.     {
  25.         state.InputEnabled=true;
  26.     }
  27. }

工作機制

wrapper產生器使用System.Reflection.Emit來產生一個代理類,這個代理類包含介面所需的所有方法,同時他也包含訪問屬性
的方法,每個方法都有一個特定的簽名(signature)。

在方法體裡面,如果InvokeRequired的傳回值是false,那麼就直接調用原始的方法。這個檢查是很重要的,為了如果form還沒有
attached到一個訊息線程的時候,調用也能夠工作。

如果InvokeRequired返回true,那麼一個指向原始的方法的delegate當作是調用這個form的Invode方法的參數傳遞進去。delegate
的類型被緩衝,這樣對於相同簽名的方法,不會重複建立delegate類型。

因為wrapper產生器使用ISynchronizeInvoke這個介面來進行同步調用,所以你可以在非windows-forms的程式裡面來使用。你要做的
只是實現介面和大概其自己實現一個類似訊息佇列的東西。

局限性和一些警告

需要理解的一個很重要的事情就是,使用安全執行緒的wrapper把線程同步這個事情給隱藏起來了,但是並不意味著沒有做線程同步。
所以,如果使用安全執行緒的wrapper來訪問一個屬性,在InvokeRequired返回ture的情況下,比直接存取這個屬性要慢的多。因此,
如果你從幾個不同的線程裡面對你的form做複雜的改動,最好是把他們放到一個方法裡面一次完成,而不是分開幾次來進行調用。

另外一個需要牢記在心的是,不是所有的類型在不進行同步的情況下進行線程間傳遞都是安全的。通常,只有類似int,DateTime,和
一些immutable reference類型,比如string,是安全的。如果你要從一個線程向另一個線程傳遞一個mutable reference類型,比如
StringBuilder,那麼你一定要小心。如果這個object沒有在不同線程裡面改動,或者他本身是安全執行緒的,那麼傳遞是ok的。如果有
任何疑問,做一個深拷貝傳遞好了,別使用引用。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.