原文地址:
http://www.cnblogs.com/Kevin-moon/archive/2009/01/13/1374353.html 過年前的這段時(shí)間真是舒服,沒有了平時(shí)項(xiàng)目發(fā)版的緊張,剩下的就是只有在網(wǎng)上閑逛了,哈哈!

今天早上閑逛的時(shí)候,在CodeProject發(fā)現(xiàn)了個(gè)不錯(cuò)的文章,英文好的直接去
http://www.codeproject.com/KB/threads/SynchronizationContext.aspx看吧,不好,就將就的看下我的吧,呵呵!(沒有直接翻譯,不過大概的思路相同)
理解SynchronizationContext
SynchronizationContext 類是一個(gè)基類,可提供不帶同步的自由線程上下文。 此類實(shí)現(xiàn)的同步模型的目的是使公共語言運(yùn)行庫內(nèi)部的異步/同步操作能夠針對不同的異步模型采取正確的行為。此模型還簡化了托管應(yīng)用程序?yàn)樵诓煌耐江h(huán)境下正常工作而必須遵循的一些要求。同步模型的提供程序可以擴(kuò)展此類并為這些方法提供自己的實(shí)現(xiàn)。(來自MSDN)
簡而言之就是允許一個(gè)線程和另外一個(gè)線程進(jìn)行通訊,SynchronizationContext在通訊中充當(dāng)傳輸者的角色。另外這里有個(gè)地方需要清楚的,不是每個(gè)線程都附加SynchronizationContext這個(gè)對象,只有UI線程是一直擁有的。
這里你可能有個(gè)問題:對于UI線程來說,是如何將SynchronizationContext這個(gè)對象附加到線程上的呢?!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());
}
運(yùn)行結(jié)果:
1、No context for this thread
2、We got a context
從運(yùn)行結(jié)果來看,在Form1 form = new Form1()之前,SynchronizationContext對象是為空,而當(dāng)實(shí)例化Form1窗體后,SynchronizationContext對象就被附加到這個(gè)線程上了。所以可以得出答案了:當(dāng)Control對象被創(chuàng)建的同時(shí),SynchronizationContext對象也會(huì)被創(chuàng)建并附加到線程上。
好的,我們既然已經(jīng)基本了解了SynchronizationContext,接下來的事情就是使用它了!
如何使用SynchronizationContext
應(yīng)用程序有兩個(gè)線程:線程A和線程B,不過線程B比較特殊,它屬于UI線程,當(dāng)這兩個(gè)線程同時(shí)運(yùn)行的時(shí)候,線程A有個(gè)需求:"修改UI對象的屬性",這時(shí)候如果你是線程A,你會(huì)如何去完成需求呢?!
第一種方式:
在線程A上面直接去操作UI對象,這是線程B說:"線程A,你真xx,你不知道我的特殊嘛!",然后直接拋給線程A一個(gè)異常信息,線程A得到異常后,一臉的無辜和無奈.....!
第二種方式:
InvokeRequired?!是的,當(dāng)然沒問題。(解釋下,InvokeRequired屬性是每個(gè)Control對象都具有的屬性,它會(huì)返回true和false,當(dāng)是true的時(shí)候,表示它在另外一個(gè)線程上面,這是必須通過Invoke,BeginInvoke這些方法來調(diào)用更新UI對象的方法,當(dāng)是false的時(shí)候,有兩種情況,1:位于當(dāng)前線程上面,可以通過直接去調(diào)用修改UI對象的方法,2:位于不同的線程上,不過控件或窗體的句柄不存在。對于句柄是否存在的判斷,可以通過IsHandleCreated來獲取,如果句柄不存在,是不能調(diào)用Invoke...這些方法的,這時(shí)候你必須等待句柄的創(chuàng)建)
通過InvokeRequired的實(shí)現(xiàn)方式如下:
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(72, 160);
myButton.Size = new Size(152, 32);
myButton.TabIndex = 1;
myButton.Text = "Add items in list box";
myButton.Click += new EventHandler(Button_Click);
myListBox.Location = new Point(48, 32);
myListBox.Name = "myListBox";
myListBox.Size = new Size(200, 95);
myListBox.TabIndex = 2;
ClientSize = new Size(292, 273);
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});
}
}
}
不過這里存在一個(gè)有爭論的地方:這種方式必須通過調(diào)用Control的Invoke方法來實(shí)現(xiàn),這就是說調(diào)用的地方必須有一個(gè)Control的引用存在。
看下MyThreadClass類,這個(gè)類中就存在MyFormControl的引用對象。其實(shí)如果這個(gè)類放在這里是沒有任務(wù)不妥之處的,但是如果把MyThreadClass類放在業(yè)務(wù)層,這時(shí)候問題就出現(xiàn)了,從設(shè)計(jì)角度來說,業(yè)務(wù)層是不允許和UI有任何關(guān)系,所以MyFormControl的引用對象絕對不能存在于MyThreadClass類,但是不讓它存在,更新UI控件的需求就滿足不了,這種情況下,我們?nèi)绾巫龅揭环N最佳方案呢!?
第三種方式:
本文的主角: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);
}
}
運(yùn)行結(jié)果:
mToolStripButtonThreads_Click thread: 10
Run thread: 3
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
(x1000 times)
程序首先在Form1窗體的mToolStripButtonThreads_Click事件中,獲取當(dāng)前的SynchronizationContext對象,然后啟動(dòng)另外一個(gè)線程,并且將SynchronizationContext對象傳遞給啟動(dòng)的線程,啟動(dòng)的線程通過SynchronizationContext對象的Post方法來調(diào)用一個(gè)委托方法UpdateUI,因?yàn)閁pdateUI是執(zhí)行在主UI線程上的,所以可以通過它來修改UI上對象的信息。
怎么樣!不錯(cuò)吧,現(xiàn)在我們可以把Control引用給拋棄了,哈哈!
如果你去查下MSDN,會(huì)發(fā)現(xiàn)SynchronizationContext還有一個(gè)Send方法,Send和Post有什么區(qū)別?
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");
}
當(dāng)你運(yùn)行的時(shí)候, 你可能希望在UI線程上面去拋出,但是結(jié)果往往出忽你的意料,異常信息都在Run方法的線程上被捕獲了。這時(shí)候你可能想問:WHY?!
解釋之前,我們先看下,Send VS Post的結(jié)果:
Send 方法啟動(dòng)一個(gè)同步請求以發(fā)送消息
Post 方法啟動(dòng)一個(gè)異步請求以發(fā)送消息。
哈哈,異常處理的答案迎韌而解了吧!
今天就寫到這里吧,下一篇和大家討論下SynchronizationContext是否在所有線程中都適用...
posted on 2010-09-07 12:10
漂漂 閱讀(960)
評論(1) 編輯 收藏 引用 所屬分類:
c#開發(fā)