線程之間的通訊---SynchronizationContext [轉]

(轉載自http://www.cnblogs.com/Kevin-moon/archive/2009/01/13/1374353.html)

(英文相關:http://www.codeproject.com/KB/threads/SynchronizationContext.aspx)

理解SynchronizationContext

  SynchronizationContext 類是一個基類,可提供不帶同步的自由線程上下文。 此類實現的同步模型的目的是使公共語言運行庫內部的異步/同步操作能夠針對不同的異步模型採取正確的行爲。此模型還簡化了託管應用程序爲在不同的同步環境下正常工作而必須遵循的一些要求。同步模型的提供程序可以擴展此類併爲這些方法提供自己的實現。(來自MSDN)
  簡而言之就是允許一個線程和另外一個線程進行通訊,SynchronizationContext在通訊中充當傳輸者的角色。另外這裏有個地方需要清楚的,不是每個線程都附加SynchronizationContext這個對象,只有UI線程是一直擁有的。
  這裏你可能有個問題:對於UI線程來說,是如何將SynchronizationContext這個對象附加到線程上的呢?!OK,我們先從下面的代碼開始,

 

[STAThread]
static void
 Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(
false
);

    
// let's check the context here

    var context = SynchronizationContext.Current;
    
if (context == null
)
        MessageBox.Show(
"No context for this thread"
);
    
else

        MessageBox.Show(
"We got a context");

    
// create a form

    Form1 form = new Form1();

    
// let's check it again after creating a form

    context = SynchronizationContext.Current;

    
if (context == null
)
        MessageBox.Show(
"No context for this thread"
);
    
else

        MessageBox.Show(
"We got a context");

    
if (context == null
)
        MessageBox.Show(
"No context for this thread"
);

    Application.Run(
new
 Form1());
}

運行結果:
1、No context for this thread
2、We got a context

     
     從運行結果來看,在Form1 form = new Form1()之前,SynchronizationContext對象是爲空,而當實例化Form1窗體後,SynchronizationContext對象就被附加到這個線程上了。所以可以得出答案了:當Control對象被創建的同時,SynchronizationContext對象也會被創建並附加到線程上。
     好的,我們既然已經基本瞭解了SynchronizationContext,接下來的事情就是使用它了!

如何使用SynchronizationContext

  應用程序有兩個線程:線程A和線程B,不過線程B比較特殊,它屬於UI線程,當這兩個線程同時運行的時候,線程A有個需求:"修改UI對象的屬性",這時候如果你是線程A,你會如何去完成需求呢?!

第一種方式:
     
     在線程A上面直接去操作UI對象,這是線程B說:"線程A,你真xx,你不知道我的特殊嘛!",然後直接拋給線程A一個異常信息,線程A得到異常後,一臉的無辜和無奈.....!

第二種方式:
  InvokeRequired?!是的,當然沒問題。(解釋下,InvokeRequired屬性是每個Control對象都具有的屬性,它會返回true和false,當是true的時候,表示它在另外一個線程上面,這是必須通過Invoke,BeginInvoke這些方法來調用更新UI對象的方法,當是false的時候,有兩種情況,1:位於當前線程上面,可以通過直接去調用修改UI對象的方法,2:位於不同的線程上,不過控件或窗體的句柄不存在。對於句柄是否存在的判斷,可以通過IsHandleCreated來獲取,如果句柄不存在,是不能調用Invoke...這些方法的,這時候你必須等待句柄的創建
通過InvokeRequired的實現方式如下:

using System;
using
 System.Drawing;
using
 System.Windows.Forms;
using
 System.Threading;

   
public class
 MyFormControl : Form
   {
      
public delegate void
 AddListItem(String myString);
      
public
 AddListItem myDelegate;
      
private
 Button myButton;
      
private
 Thread myThread;
      
private
 ListBox myListBox;
      
public
 MyFormControl()
      {
         myButton 
= new
 Button();
         myListBox 
= new
 ListBox();
         myButton.Location 
= new Point(72160
);
         myButton.Size 
= new Size(15232
);
         myButton.TabIndex 
= 1
;
         myButton.Text 
= "Add items in list box"
;
         myButton.Click 
+= new
 EventHandler(Button_Click);
         myListBox.Location 
= new Point(4832
);
         myListBox.Name 
= "myListBox"
;
         myListBox.Size 
= new Size(20095
);
         myListBox.TabIndex 
= 2
;
         ClientSize 
= new Size(292273
);
         Controls.AddRange(
new
 Control[] {myListBox,myButton});
         Text 
= " 'Control_Invoke' example "
;
         myDelegate 
= new
 AddListItem(AddListItemMethod);
      }
      
static void
 Main()
      {
         MyFormControl myForm 
= new
 MyFormControl();
         myForm.ShowDialog();
      }
      
public void
 AddListItemMethod(String myString)
      {
            myListBox.Items.Add(myString);
      }
      
private void Button_Click(object
 sender, EventArgs e)
      {
         myThread 
= new Thread(new
 ThreadStart(ThreadFunction));
         myThread.Start();
      }
      
private void
 ThreadFunction()
      {
         MyThreadClass myThreadClassObject  
= new MyThreadClass(this
);
         myThreadClassObject.Run();
      }
   }
   
public class
 MyThreadClass
   {
      MyFormControl myFormControl1;
      
public
 MyThreadClass(MyFormControl myForm)
      {
         myFormControl1 
=
 myForm;
      }
      String myString;

      
public void
 Run()
      {
         
for (int i = 1; i <= 5; i++
)
         {
            myString 
= "Step number " + i.ToString() + " executed"
;
            Thread.Sleep(
400
);
            
//
 Execute the specified delegate on the thread that owns
            
//
 'myFormControl1' control's underlying window handle with
            
// the specified list of arguments.

            myFormControl1.Invoke(myFormControl1.myDelegate,
                                   
new
 Object[] {myString});
         }
      }
   }

    不過這裏存在一個有爭論的地方:這種方式必須通過調用Control的Invoke方法來實現,這就是說調用的地方必須有一個Control的引用存在。
  看下MyThreadClass類,這個類中就存在MyFormControl的引用對象。其實如果這個類放在這裏是沒有任務不妥之處的,但是如果把MyThreadClass類放在業務層,這時候問題就出現了,從設計角度來說,業務層是不允許和UI有任何關係,所以MyFormControl的引用對象絕對不能存在於MyThreadClass類,但是不讓它存在,更新UI控件的需求就滿足不了,這種情況下,我們如何做到一種最佳方案呢!?

第三種方式:
  本文的主角:SynchronizationContext登場了。解釋之前,先讓下面的代碼做下鋪墊,
public partial class Form1 : Form
{
    
public
 Form1()
    {
        InitializeComponent();
    }

    
private void mToolStripButtonThreads_Click(object
 sender, EventArgs e)
    {
        
// let's see the thread id

        int id = Thread.CurrentThread.ManagedThreadId;
        Trace.WriteLine(
"mToolStripButtonThreads_Click thread: " +
 id);

        
//
 grab the sync context associated to this
        
//
 thread (the UI thread), and save it in uiContext
        
//
 note that this context is set by the UI thread
        
//
 during Form creation (outside of your control)
        
// also note, that not every thread has a sync context attached to it.

        SynchronizationContext uiContext = SynchronizationContext.Current;

        
// create a thread and associate it to the run method

        Thread thread = new Thread(Run);

        
//
 start the thread, and pass it the UI context,
        
//
 so this thread will be able to update the UI
        
// from within the thread

        thread.Start(uiContext);
    }

    
private void Run(object
 state)
    {
        
// lets see the thread id

        int id = Thread.CurrentThread.ManagedThreadId;
        Trace.WriteLine(
"Run thread: " +
 id);

        
// grab the context from the state

        SynchronizationContext uiContext = state as SynchronizationContext;

        
for (int i = 0; i < 1000; i++
)
        {
            
//
 normally you would do some code here
            
//
 to grab items from the database. or some long
            
// computation

            Thread.Sleep(10);

            
//
 use the ui context to execute the UpdateUI method,
            
// this insure that the UpdateUI method will run on the UI thread.


            uiContext.Post(UpdateUI, 
"line " + i.ToString());
        }
    }

    
/// <summary>

    
/// This method is executed on the main UI thread.
    
/// </summary>

    private void UpdateUI(object state)
    {
        
int id =
 Thread.CurrentThread.ManagedThreadId;
        Trace.WriteLine(
"UpdateUI thread:" +
 id);
        
string text = state as string
;
        mListBox.Items.Add(text);
    }
}

 

運行結果:

mToolStripButtonThreads_Click thread: 10
Run thread: 
3
UpdateUI thread:
10
UpdateUI thread:
10
UpdateUI thread:
10
UpdateUI thread:
10
 (x1000 times)

    程序首先在Form1窗體的mToolStripButtonThreads_Click事件中,獲取當前的SynchronizationContext對象,然後啓動另外一個線程,並且將SynchronizationContext對象傳遞給啓動的線程,啓動的線程通過SynchronizationContext對象的Post方法來調用一個委託方法UpdateUI,因爲UpdateUI是執行在主UI線程上的,所以可以通過它來修改UI上對象的信息。
    怎麼樣!不錯吧,現在我們可以把Control引用給拋棄了,哈哈!
    如果你去查下MSDN,會發現SynchronizationContext還有一個Send方法,Send和Post有什麼區別?

Send VS Post,以及異常處理 首先看下異常處理的情況

private void Run(object state)
{
    
// let's see the thread id

    int id = Thread.CurrentThread.ManagedThreadId;
    Trace.WriteLine(
"Run thread: " +
 id);

    
// grab the context from the state

    SynchronizationContext uiContext = state as SynchronizationContext;

    
for (int i = 0; i < 1000; i++
)
    {
        Trace.WriteLine(
"Loop " +
 i.ToString());
        
//
 normally you would do some code here
        
//
 to grab items from the database. or some long
        
// computation

        Thread.Sleep(10);

        
//
 use the ui context to execute the UpdateUI method, this insure that the
        
// UpdateUI method will run on the UI thread.


        
try
        {
            uiContext.Send(UpdateUI, 
"line " + i.ToString());
        }
        
catch
 (Exception e)
        {
            Trace.WriteLine(e.Message);
        }
    }
}

/// <summary>

/// This method is executed on the main UI thread.
/// </summary>

private void UpdateUI(object state)
{
    
throw new Exception("Boom"
);
}

   當你運行的時候, 你可能希望在UI線程上面去拋出,但是結果往往出忽你的意料,異常信息都在Run方法的線程上被捕獲了。這時候你可能想問:WHY?!
   解釋之前,我們先看下,Send VS Post的結果:
   Send 方法啓動一個同步請求以發送消息
   Post 方法啓動一個異步請求以發送消息。    
   哈哈,異常處理的答案迎韌而解了吧!




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章