Skip to main content
 首页 » 编程设计

c# 线程间同步 (转)

2022年07月19日136小虾米

理解SynchronizationContext
--------------------------------------------------------------------------------
   SynchronizationContext 类是一个基类,可提供不带同步的自由线程上下文。 此类实现的同步模型的目的是使公共语言运行库内部的异步/同步操作能够针对不同的异步模型采取正确的行 为。此模型还简化了托管应用程序为在不同的同步环境下正常工作而必须遵循 的一些要求。同步模型的提供程序可以扩展此类并为这些方法提供自己的实现。(来自MSDN)

  简而言之就是允许一个线程和另外一个线程 进行通讯,SynchronizationContext在通讯中充当传输者的角色。另外这里有个地方需要清楚的,不是每个线程都附加 SynchronizationContext这个对象,只有UI线程是一直拥有的。

  这里你可 能有个问题:对于UI线程来说,是如何将SynchronizationContext这个对象附加到线程上的呢?!OK,我们先从下面的代码开始,

  1. [STAThread]
  2. static void Main()
  3. {
  4.     Application.EnableVisualStyles();
  5.     Application.SetCompatibleTextRenderingDefault(false);
  6.     // let's check the context here
  7.     var context = SynchronizationContext.Current;
  8.     if (context == null)
  9.         MessageBox.Show("No context for this thread");
  10.     else
  11.         MessageBox.Show("We got a context");
  12.     // create a form
  13.     Form1 form = new Form1();
  14.     // let's check it again after creating a form
  15.     context = SynchronizationContext.Current;
  16.     if (context == null)
  17.         MessageBox.Show("No context for this thread");
  18.     else
  19.         MessageBox.Show("We got a context");
  20.     if (context == null)
  21.         MessageBox.Show("No context for this thread");
  22.     Application.Run(new Form1());
  23. }
复制代码

运行结 果:
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的实现方式如下:

  1. using System;
  2. using System.Drawing;
  3. using System.Windows.Forms;
  4. using System.Threading;
  5.   public class MyFormControl : Form
  6.   {
  7.       public delegate void AddListItem(String myString);
  8.       public AddListItem myDelegate;
  9.       private Button myButton;
  10.       private Thread myThread;
  11.       private ListBox myListBox;
  12.       public MyFormControl()
  13.       {
  14.         myButton = new Button();
  15.         myListBox = new ListBox();
  16.         myButton.Location = new Point(72, 160);
  17.         myButton.Size = new Size(152, 32);
  18.         myButton.TabIndex = 1;
  19.         myButton.Text = "Add items in list box";
  20.         myButton.Click += new EventHandler(Button_Click);
  21.         myListBox.Location = new Point(48, 32);
  22.         myListBox.Name = "myListBox";
  23.         myListBox.Size = new Size(200, 95);
  24.         myListBox.TabIndex = 2;
  25.         ClientSize = new Size(292, 273);
  26.         Controls.AddRange(new Control[] {myListBox,myButton});
  27.         Text = " 'Control_Invoke' example ";
  28.         myDelegate = new AddListItem(AddListItemMethod);
  29.       }
  30.       static void Main()
  31.       {
  32.         MyFormControl myForm = new MyFormControl();
  33.         myForm.ShowDialog();
  34.       }
  35.       public void AddListItemMethod(String myString)
  36.       {
  37.             myListBox.Items.Add(myString);
  38.       }
  39.       private void Button_Click(object sender, EventArgs e)
  40.       {
  41.         myThread = new Thread(new ThreadStart(ThreadFunction));
  42.         myThread.Start();
  43.       }
  44.       private void ThreadFunction()
  45.       {
  46.         MyThreadClass myThreadClassObject  = new MyThreadClass(this);
  47.         myThreadClassObject.Run();
  48.       }
  49.   }
  50.   public class MyThreadClass
  51.   {
  52.       MyFormControl myFormControl1;
  53.       public MyThreadClass(MyFormControl myForm)
  54.       {
  55.         myFormControl1 = myForm;
  56.       }
  57.       String myString;
  58.       public void Run()
  59.       {
  60.         for (int i = 1; i <= 5; i++)
  61.         {
  62.             myString = "Step number " + i.ToString() + " executed";
  63.             Thread.Sleep(400);
  64.             // Execute the specified delegate on the thread that owns
  65.             // 'myFormControl1' control's underlying window handle with
  66.             // the specified list of arguments.
  67.             myFormControl1.Invoke(myFormControl1.myDelegate,
  68.                                   new Object[] {myString});
  69.         }
  70.       }
  71.   }
复制代码

不过这里 存在一个有争论的地方:这种方式必须通过调用Control的Invoke方法来实现,这就是说调用的地方必须有一个Control的引用存在。

   看下MyThreadClass类,这个类中就存在MyFormControl的引用对象。其实如果这个类放在这里是没有任务不妥之处的,但是如果把 MyThreadClass类放在业务层,这时候问题就出现了,从设计角度来说,业务层是不允许和UI有任何关系,所以MyFormControl的引用 对象绝对不能存在于MyThreadClass类,但是不让它存在,更新UI控件的需求就满足不了,这种情况下,我们如何做到一种 最佳方案呢!?

第三种方式:

  本文的主角:SynchronizationContext登场了。解释 之前,先让下面的代码做下铺垫,

  1. public partial class Form1 : Form
  2. {
  3.     public Form1()
  4.     {
  5.         InitializeComponent();
  6.     }
  7.     private void mToolStripButtonThreads_Click(object sender, EventArgs e)
  8.     {
  9.         // let's see the thread id
  10.         int id = Thread.CurrentThread.ManagedThreadId;
  11.         Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id);
  12.         // grab the sync context associated to this
  13.         // thread (the UI thread), and save it in uiContext
  14.         // note that this context is set by the UI thread
  15.         // during Form creation (outside of your control)
  16.         // also note, that not every thread has a sync context attached to it.
  17.         SynchronizationContext uiContext = SynchronizationContext.Current;
  18.         // create a thread and associate it to the run method
  19.         Thread thread = new Thread(Run);
  20.         // start the thread, and pass it the UI context,
  21.         // so this thread will be able to update the UI
  22.         // from within the thread
  23.         thread.Start(uiContext);
  24.     }
  25.     private void Run(object state)
  26.     {
  27.         // lets see the thread id
  28.         int id = Thread.CurrentThread.ManagedThreadId;
  29.         Trace.WriteLine("Run thread: " + id);
  30.         // grab the context from the state
  31.         SynchronizationContext uiContext = state as SynchronizationContext;
  32.         for (int i = 0; i < 1000; i++)
  33.         {
  34.             // normally you would do some code here
  35.             // to grab items from the database. or some long
  36.             // computation
  37.             Thread.Sleep(10);
  38.             // use the ui context to execute the UpdateUI method,
  39.             // this insure that the UpdateUI method will run on the UI thread.
  40.             uiContext.Post(UpdateUI, "line " + i.ToString());
  41.         }
  42.     }
  43.     ///
  44.     /// This method is executed on the main UI thread.
  45.     ///
  46.     private void UpdateUI(object state)
  47.     {
  48.         int id = Thread.CurrentThread.ManagedThreadId;
  49.         Trace.WriteLine("UpdateUI thread:" + id);
  50.         string text = state as string;
  51.         mListBox.Items.Add(text);
  52.     }
  53. }
复制代码

运行结 果:

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,以及异常处理
--------------------------------------------------------------------------------
首 先看下异常处理的情况

  1. private void Run(object state)
  2. {
  3.     // let's see the thread id
  4.     int id = Thread.CurrentThread.ManagedThreadId;
  5.     Trace.WriteLine("Run thread: " + id);
  6.     // grab the context from the state
  7.     SynchronizationContext uiContext = state as SynchronizationContext;
  8.     for (int i = 0; i < 1000; i++)
  9.     {
  10.         Trace.WriteLine("Loop " + i.ToString());
  11.         // normally you would do some code here
  12.         // to grab items from the database. or some long
  13.         // computation
  14.         Thread.Sleep(10);
  15.         // use the ui context to execute the UpdateUI method, this insure that the
  16.         // UpdateUI method will run on the UI thread.
  17.         try
  18.         {
  19.             uiContext.Send(UpdateUI, "line " + i.ToString());
  20.         }
  21.         catch (Exception e)
  22.         {
  23.             Trace.WriteLine(e.Message);
  24.         }
  25.     }
  26. }
  27. ///
  28. /// This method is executed on the main UI thread.
  29. ///
  30. private void UpdateUI(object state)
  31. {
  32.     throw new Exception("Boom");
  33. }
复制代码

当你运行 的时候, 你可能希望在UI线程上面去抛出,但是结果往往出忽你的意料,异常信息都在Run方法的线程上被捕获了。这时候你可能想问:WHY?!

解 释之前,我们先看下,Send VS Post的结果:
Send 方法启动一个同步请求以发送消息
Post 方法启动一个异步请求以发送消息。

http://blog.chinaunix.net/uid-20093213-id-1977886.html


本文参考链接:https://www.cnblogs.com/xihong2014/p/13512252.html