理解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的时候,表示可以去修改UI对
象,反之则不允许。通过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(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});
- }
- }
- }
复制代码不过这里
存在一个有争论的地方:这种方式必须通过调用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());
- }
- }
- ///
- /// This method is executed on the main UI thread.
- ///
- 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);
- }
- }
- }
- ///
- /// This method is executed on the main UI thread.
- ///
- private void UpdateUI(object state)
- {
- throw new Exception("Boom");
- }
复制代码当你运行
的时候, 你可能希望在UI线程上面去抛出,但是结果往往出忽你的意料,异常信息都在Run方法的线程上被捕获了。这时候你可能想问:WHY?!
解
释之前,我们先看下,Send VS Post的结果:
Send 方法启动一个同步请求以发送消息
Post
方法启动一个异步请求以发送消息。
阅读(1338) | 评论(1) | 转发(0) |