zoukankan      html  css  js  c++  java
  • 利用SynchronizationContext.Current在线程间同步上下文

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

    在多线程操作时往往需要切回某个线程中去工作,等完成后再切回来。如主UI线程中创建了一个子线程A。A中添加了委托事件。UI线程中向A线程的类注册了事件,当A线程触发事件时去修改UI上的属性如TEXT。这个时候往往要在UI线程向子线程注册的事件方法中使用控件的invoke方法才能访问UI线程中的控件,因为这些注册的事件(委托)方法代码虽然看似写在UI线程的Form类中,但实际上是注册在了子线程A的事件中,它们是会被子线程A触发事件时在子线程内部执行的。这样,我们不得不在主UI线程的类的注册事件方法中通过控件的invoke方法才能访问控件,这样做十分麻烦。我们想和系统的控件事件一样,直接在注册的事件方法中访问控件。那么我们想,有没有一个机制能让子线程在执行UI线程委托过来的(注册的事件)方法中直接访问控件本身呢?答案是肯定的。对过SynchronizationContext.Current 。

    SynchronizationContext.Current 能得到当前被主UI线程接管过的对象SynchronizationContext。

    这个对象有一个方法:SynchronizationContext.Send(SendOrPostCallback d,object state)

    d 为一个没有返回值,并且具有一个Object类型传入参数的委托(SendOrPostCallback )

    state 为执行这个委托时的参数(object)

    你现在需要在子线程运行的时候利用SynchronizationContext.Current 得到SynchronizationContext对象,然后在需要切回UI线程的地方使用:

    SynchronizationContext.Send(SendOrPostCallback d,object state);就可实现将委托的方法切回到UI线程上去执行。

    注意:SynchronizationContext的对象不是所有线程都被附加的,只有UI主线程会被附加。对于UI线程来说,是如何将SynchronizationContext这个对象附加到线程上的呢?

    在Form1 form = new Form1()之前,SynchronizationContext对象是为空,而当实例化Form1窗体后,SynchronizationContext对象就被附加到这个线程上了。所以可以得出答案了:当Control对象被创建的同时,SynchronizationContext对象也会被创建并附加到线程上。所有在使用时,一定要等窗体InitializeComponent(); 这个完成后 它才能得到一个不这NULL的对象

    可以参考这里:http://lzy3169421.blog.163.com/blog/static/1135452772009357251417/

    最后SynchronizationContext的Sendt()和Post()二个方法的区别:

    Send() 是简单的在当前线程上去调用委托来实现(同步调用)。也就是在子线程上直接调用UI线程执行,等UI线程执行完成后子线程才继续执行。

    Post() 是在线程池上去调用委托来实现(异步调用)。这是子线程会从线程池中找一个线程去调UI线程,子线程不等待UI线程的完成而直接执行自己下面的代码。

    详细的推荐看这二篇文章:

    线程之间的通讯---SynchronizationContext

    奇妙的SynchronizationContext

    [csharp] view plain copy
     
    1. //在类里声明SynchronizationContext ,如果其它类要访问它,可将它声明为静态  
    2. SynchronizationContext sync;  
    3.   
    4. //-----------------------------------------  
    5.   
    6. //在UI线程可以接触到这个类的地方得到它  
    7. sync = SynchronizationContext.Current;  
    8.   
    9.   
    10. //----------------------------------------  
    11.   
    12.   
    13. //声明一个要带回给UI线程的委托,然后使用SynchronizationContext的Send方法  
    14. //PushNewMsgEvent 是一个定义好的事件  
    15. private void PushNewMsg(object sender, InfoBox iBox)  
    16.         {//这个方法是被子线程执行的。  
    17.             if (PushNewMsgEvent != null)  
    18.             {  
    19.                   
    20.                 SendOrPostCallback sp = new SendOrPostCallback(aaa);    //声明SynchronizationContext.Send方法里需要用到的委托  
    21.                 List<object> tmp = new List<object>(); //将多个参数封装到一个对象中,以便给委托传递object参数  
    22.                 tmp.Add(sender);  
    23.                 tmp.Add(iBox);  
    24.                   
    25.                 sync.Send(sp, tmp);    //发送给主UI线程来执行sp这个委托  
    26.         
    27.             }  
    28.         }  
    29.   
    30. //一个要带回给UI线程执行的委托方法  
    31.         private void aaa(object obj)  
    32.         {//这个方法如果没有上面的SynchronizationContext.Send,也将是在子线程中执行,但由于上面的把这个方法委托给了主UI去执行,所以它将被运行在UI线程上              
    33.                 List<object> tmp = (List<object>)obj;   
    34.                 User user = (User)tmp[0];   
    35.                 InfoBox ib = (InfoBox)tmp[1];   
    36.                 PushNewMsgEvent(user, ib); //这里已经变成在主UI线程中来触发执行这个事件了,不再是在子线程中触发。   
    37.         }  

    ···

    #################################

    下面是一个自己做的例子。它将模仿WINDOWS窗口编程中的事件,由子线程触发事件,触发的事件又由消息同步发送到UI线程上来,最终在UI线程中处理事件。

    [csharp] view plain copy
     
    1. using System;  
    2. using System.Threading;  
    3.   
    4. namespace NewMeteMonitor  
    5. {  
    6.     /// <summary>  
    7.     /// 利用SynchronizationContext进行子线程与UI线程的通讯。  
    8.     /// 即向UI线程发送子线程中的内容(线程安全的方式)  
    9.     /// 学习FORM窗体中的事件,将子线程封装在一个类中,并通过这个类中的事件将内容发回UI线程。  
    10.     /// 2016-03-28 by bob  
    11.     /// </summary>  
    12.     class SyncTest  
    13.     {  
    14.         public event EventHandler<EventAgrsBob> BackEvent;  
    15.         public EventAgrsBob myAgrs = new EventAgrsBob();  
    16.         private SynchronizationContext sync = null;  
    17.         private Thread subThread;          
    18.   
    19.         public SyncTest()  
    20.         {  
    21.             //由于SyncTest类的对象通常是由UI来创建的,所有执行到这里时还是是UI线程中的。  
    22.             //因此可以在这个类的构建方法中直接获得UI线程的线程上下文SynchronizationContext  
    23.             sync = SynchronizationContext.Current;  
    24.   
    25.             if (sync==null)  
    26.             {  
    27.                 throw new NullReferenceException("by bob. SynchronizationContext对象为空。请在UI线程的方法中创建该对象,不要在非UI线程创建它,也不要在类的成员声明时直接创建该对象,因为在类的成员声明时.NET还没有构建UI线程的SynchronizationContext对象。");  
    28.             }  
    29.         }  
    30.   
    31.         /// <summary>  
    32.         /// 外部(UI线程)的调用入口  
    33.         /// </summary>  
    34.         public void Start()  
    35.         {  
    36.             subThread = new Thread(new ThreadStart(ThreadDoing));  
    37.             subThread.Start();//子线程开始工作  
    38.         }  
    39.   
    40.         /// <summary>  
    41.         /// 子线程的具体方法  
    42.         /// </summary>  
    43.         private void ThreadDoing()  
    44.         {  
    45.             //定义一个消息同步对象,它是一个委托,将封装了触发事件的方法sendmethod放进去。s  
    46.             SendOrPostCallback sendDelegate = new SendOrPostCallback(sendmethod);                
    47.               
    48.             for (int i = 0; i < 100; i++)  
    49.             {  
    50.                 Thread.Sleep(500);  
    51.                 myAgrs.value = i;  
    52.   
    53.                 //本来这里应该触发事件,现在由sync.Send代为触发,从而实现与UI线程通讯。这步是子线程实现与UI通讯的关键。  
    54.                 sync.Send(sendDelegate, myAgrs);  
    55.                 //要理解上面这行代码的执行。它的参数由一个封装了事件的委托sendDelegate 及一个给委托传递的对象组成。  
    56.                 //sendDelegate里的方法就是在UI线程上要处理的具体工作,myAgrs封装了信息,并借由sendDelegate的参数一并发给了UI线程。  
    57.                 //它有这样的置换关系:  
    58.                 //sync.Send(sendDelegate, myAgrs)==sendDelegate(myAgrs)-->sendmethod(myAgrs)-->BackEvent(this, myAgrs)  
    59.             }  
    60.         }  
    61.   
    62.         /// <summary>  
    63.         /// 由于线程同步消息方法 sync.Send(SendOrPostCallback d,object state)的需要,  
    64.         /// 这里必须将触发事件封装进来,因为在创建SendOrPostCallback委托对象时需要这个方法。  
    65.         /// 这个方法也很简单就是触发BackEvent事件。  
    66.         /// </summary>  
    67.         /// <param name="obj"></param>  
    68.         private void sendmethod(object obj)  
    69.         {  
    70.             BackEvent(this, (EventAgrsBob)obj); //触发事件  
    71.         }  
    72.     }  
    73.   
    74.     /// <summary>  
    75.     /// 定义一个事件参数类,只添加一个value属性 用来保存一个数。  
    76.     /// 定义的参数类必须继承于EventArgs。因为在定义事件的泛型委托:EventHandler<TEventAgrs> 中有此约束:where TEventAgrs:EventAgrs  
    77.     /// </summary>  
    78.     public class EventAgrsBob : EventArgs  
    79.     {  
    80.         public int value { get; set; }  
    81.     }  
    82.      
    83. }  



    下面是UI线程 窗体中的代码:

    [csharp] view plain copy
     
    1. using System;  
    2. using System.Windows.Forms;  
    3.   
    4. namespace NewMeteMonitor  
    5. {  
    6.     public partial class Frm_Main : Form  
    7.     {  
    8.         SyncTest test = null;  
    9.   
    10.         public Frm_Main()  
    11.         {  
    12.             InitializeComponent();  
    13.             test = new SyncTest();  
    14.             test.BackEvent += test_BackEvent;  
    15.         }  
    16.   
    17.         private void test_BackEvent(object sender, EventAgrsBob e)  
    18.         {  
    19.             this.button1.Text = e.value.ToString();//test内部的子线程工作时通过BackEvent事件将消息回传到这里。  
    20.         }  
    21.   
    22.         private void button1_Click(object sender, EventArgs e)  
    23.         {              
    24.             test.Start(); //按钮按下启动对象内部的子线程工作  
    25.         }  
    26.     }  
    27. }  
  • 相关阅读:
    mysql正则表达式
    阿里云OSS 获取目录下所有文件
    docker 部署mvc项目 <四>
    docker部署项目 <三>
    docker 安装mysql数据库 <二>
    docker安装 <一>
    安装 Docker <一>
    Mongodb字段自增长
    EF的使用<三>
    EF 简单介绍<一>
  • 原文地址:https://www.cnblogs.com/micro-chen/p/7724309.html
Copyright © 2011-2022 走看看