[转帖]线程之间的通讯---SynchronizationContext

近日在研究webabcd的silverlight socket聊天室时,发现一个很眼生的东东SynchronizationContext(它不认得我,我也不认得它,哈哈),摆渡了下,发现园子里有一位兄弟的文章解释得不错,转贴与此,原文:http://www.cnblogs.com/Kevin-moon/archive/2009/01/13/1374353.html

 

理解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(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());
        }
    }

    /// <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 方法启动一个异步请求以发送消息。    
   哈哈,异常处理的答案迎韧而解了吧!

    今天就写到这里吧,下一篇和大家讨论下SynchronizationContext是否在所有线程中都适用...

 

时间: 2024-09-22 12:45:51

[转帖]线程之间的通讯---SynchronizationContext的相关文章

线程之间的通讯---SynchronizationContext

过年前的这段时间真是舒服,没有了平时项目发版的紧张,剩下的就是只有在网上闲逛了,哈哈! 今天早上闲逛的时候,在CodeProject发现了个不错的文章,英文好的直接去http://www.codeproject.com/KB/threads/SynchronizationContext.aspx看吧,不好,就将就的看下我的吧,呵呵!(没有直接翻译,不过大概的思路相同) 理解SynchronizationContext SynchronizationContext 类是一个基类,可提供不带同步的自

Java 线程之间的通讯问题

问题描述 A线程的作用是生产产品B线程的作用是对A线程生产出来的产品进行2次加工A,B线程共享缓存对象C当A线程每生产一个产品,放入C中,并尝试通知B线程来取(尝试让B线程进入运行状态)B线程会不停的尝试从C中取出产品,当发现C中没有产品时,B线程会wait,等待唤醒请指点童鞋...... 解决方案 完全手写的代码,有错误的符号的话自己改下吧.大概的意思就是这样A:生产出产品就add到c中就可以Product p=.....;synchronized(c){c.add(p);c.notify()

C++/CLI程序进程之间的通讯

现在,把大型软件项目分解为一些相交互的小程序似乎变得越来越普遍,程序各部分之间的通讯可使用某种类型的通讯协议,这些程序可能运行在不同的机器上.不同的操作系统中.以不同的语言编写,但也有可能只在同一台机器上,实际上,这些程序可看成是同一程序中的不同线程.而本文主要讨论C++/CLI程序间的通讯,当然,在此是讨论进程间通讯,而不是网络通讯. 简介 试想一个包含数据库查询功能的应用,通常有一个被称为服务端的程序,等待另一个被称为客户端程序发送请求,当接收到请求时,服务端执行相应功能,并把结果(或者错误

TabHost页和子Activity之间的通讯

TabHost的各个Tab页,都是由activity组成. 现在,某个子Activity中处理数据后,要自动跳转到另外一个Tab页中.这样,需要有个TabHost页和子Activity之间的通讯的机制.   子Activity通知TabHost: 1.通过广播方式 1)在TabHost中定义广播 定义变量 protected MessageBroadcastReceiver myReceiver = null;   在onCreate中注册: 加入receiveBroadcast();   pr

使用IP地址在两个android手机之间如何通讯?

问题描述 使用IP地址在两个android手机之间如何通讯? 3g连接的基础上,能否可以分别查询200个android手机的IP地址? 能否直接使用IP 地址进行TCP通信吗? 希望大家给点建议,谢谢! 解决方案 直接P2P是不行的,因为移动上网时,是先连到运营商(如联通)的大局域网再通过网关转到公网,手机分配的IP是10.xx.xx.xx,并不是公网IP,你从外面查手机的IP都是运营商网关IP.而且两台手机也不一定在同一局域网段,也就不能保证直接互联能一定成功.解决办法还是像QQ一样,搞个服务

Java中利用管道实现线程间的通讯

在Java 语言中,提供了各种各样的输入输出流(stream),使我们能够很方便的对数据进行操作,其中,管道(pipe)流是一种特殊的流,用于在不同线程(threads)间直接传送数据.一个线程发送数据到输出管道,另一个线程从输入管道中读数据.通过使用管道,实现不同线程间的通讯.无需求助于类似临时文件之类的东西.本文在简要介绍管道的基本概念后,将以一个具体的实例pipeapp加以详细说明. 1.管道的创建与使用 Java提供了两个特殊的专门的类专门用于处理管道,它们就是pipedinputstr

java线程间通信[实现不同线程之间的消息传递(通信),生产者和消费者模型]

线程通信,线程之间的消息传递: 多个线程在操作同一个资源,但对共享资源的操作动作不同:它们共享同一个资源,互为条件,相互依赖,相互通信让任务向前推进. 线程的同步,可以解决并发更新同一个资源,实现线程同步;但不能用来实现线程间的消息传递. 线程通信生产者和消费者和仓库是个典型模型: 生产者:没有生产之前通知消费者等待,生产产品结束之后,马上通知消费者消费 消费者:没有消费之前通知生产者等待,消费产品结束之后,通知生产者继续生产产品以供消费 线程通信:使用java中Object中提供的: publ

vc 基于对话框多线程编程实例——线程之间的通信

 vc基于对话框多线程编程实例--线程之间的通信 实例:  

c#-二个线程之间的区别,C#

问题描述 二个线程之间的区别,C# string ticket_list = "1,2,3,11,12"; string[] sArray = ticket_list.Split(','); if (sArray.Length > 0) { foreach (var i in sArray) { task = new Task(() => { Task a = Task.Run(() => { MyMethod(int.Parse(i)); }); }); } tas