SynchronizationContext是什么?

使用场景, 例如有Thread1和Thread2两个线程,如果需要在Thread2执行的时候在Thread1线程中执行一些代码(UI线程的更新)那么需要Thread2需要做的是拿到Thread1 的SynchronizationContext对象,然后把要执行的代码利用委托的方式通过SynchronzationContext的Post()或者Send()方法来传递给Thread1执行。线程的SynchronizationContext 都是通过SynchronizationContext.Current 这个静态属性获得。 那么是不是每个线程默认都有SynchronizationContext呢? 并不是, 目前我只知道Winfrom 和WPF 创建Control的时候才会 给主线程的SynchronizationContext赋值。如果线程中没有类似的操作 那就SynchronizationContext是Null。当然我们可以手动给线程设置一个SynchronizationContext,然而我们自己手动设置的并不能起到跨线程调用的神奇效果。原因后面讲先看代码:

【代码1】:线程中的SynchronizationContext 何时会赋值?

class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(SynchronizationContext.Current==null);
            TextBox tb = new TextBox();
            Console.WriteLine("创建Control后");
            Console.WriteLine(SynchronizationContext.Current==null);
            Console.WriteLine(SynchronizationContext.Current);
            Console.ReadKey();
        }
    }
这是一段控制台代码,从执行结果可以看看出在创建Control之前主线程的Synchronization对象是空的,在创建Control之后Synchronization存在了说明Control在创建的时候给Synchronization赋值了。有细心的朋友可能会发打印出来的类名是WindowsFormsSynchronizationContext而不是SynchronizationContext后面会解释这一状况。

【代码2】:我们如何使用SynchronizationContext来实现神奇的跨线程调用委托的功能呢?

class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("主线程:ID=" + Thread.CurrentThread.ManagedThreadId);
            TextBox tb = new TextBox(); //模拟UI线程获取SynchronizationContext对象
            Thread t1 = new Thread(Run);
            t1.Start(SynchronizationContext.Current);
            Console.ReadKey();
        }
        static void Run(object obj)
        {
            Console.WriteLine("线程t1:ID=" + Thread.CurrentThread.ManagedThreadId);
            SynchronizationContext sc = obj as SynchronizationContext;
           sc.Send(Callback, null);
           //sc.Post(Callback, null);
            Console.WriteLine("线程t1完。");
        }
        static void Callback(object obj)
        {
            Console.WriteLine("回调:ID=" + Thread.CurrentThread.ManagedThreadId);
        }
    }

这里有个问题就是不管是Send还是Post 两个都没有执行CallBack这个回调,不同的是Post打印了完成,而Send没有打印完成。那我们就知道了Send同步调用的 代码被阻塞了,Post是异步调用的 没有阻塞线程。其实吧,不管是Send还是Post都是异步调用的区别是有没有用WaitHandle机制来阻塞而已。那么他们到底为什么没有执行回调函数呢?来看看他的源代码。由【代码1】我们可以看出Control创建的时候给主线程赋值了WindowsFormsSynchronizationContext,这个类是SynchronizationContext的子类。

【代码3】

public sealed class WindowsFormsSynchronizationContext : SynchronizationContext, IDisposable
    {
        private Control controlToSendTo;

        private WindowsFormsSynchronizationContext(Control marshalingControl, Thread destinationThread)
        {
            controlToSendTo = marshalingControl;
            this.DestinationThread = destinationThread;
            Debug.Assert(controlToSendTo.IsHandleCreated, "Marshaling control should have created its handle in its ctor.");
        }
        //省略若干代码。。。。。。。

        public override void Send(SendOrPostCallback d, Object state)
        {
            Thread destinationThread = DestinationThread;
            if (destinationThread == null || !destinationThread.IsAlive)
            {
                throw new InvalidAsynchronousStateException(SR.GetString(SR.ThreadNoLongerValid));
            }

            Debug.Assert(controlToSendTo != null, "Should always have the marshaling control by this point");

            if (controlToSendTo != null)
            {
                controlToSendTo.Invoke(d, new object[] { state });
                //Send直接调用Control的Invoke
            }
        }

        public override void Post(SendOrPostCallback d, Object state)
        {
            Debug.Assert(controlToSendTo != null, "Should always have the marshaling control by this point");

            if (controlToSendTo != null)
            {
                controlToSendTo.BeginInvoke(d, new object[] { state });
                //Post直接调用Control的BeginInvoke
            }
        }

        //省略若干代码。。。。。。。
    }

说重点:从代码中可以看出 WindowsFormsSynchronizationContext  继承了SynchronizationContext 并且重写了Post()和Send()方法,而这两个方法的代码也不难懂 Post()方法就是直接调用Control 的BeginInvoke() 而Send就是直接调用了Contorl的Invoke方法。

Control的BeginInvoke 和Invoke我们 都知道 我们跨线程来更新UI的时候就会用到这两个方法,要不然抛异常。他们两个的背后又有着怎样的秘密呢?让我们来继续深挖。

【代码4】

//Control的BeginInvoke方法 都调用了 MarshaledInvoke 只是 最后一个参数不同
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public IAsyncResult BeginInvoke(Delegate method, params Object[] args)
        {
            using (new MultithreadSafeCallScope())
            {
                Control marshaler = FindMarshalingControl();
                return (IAsyncResult)marshaler.MarshaledInvoke(this, method, args, false);
            }
        }

        //Control的BeginInvoke方法 都调用了 MarshaledInvoke 只是 最后一个参数不同
        public Object Invoke(Delegate method, params Object[] args)
        {
            using (new MultithreadSafeCallScope())
            {
                Control marshaler = FindMarshalingControl();
                return marshaler.MarshaledInvoke(this, method, args, true);
            }
        }

        //Control的BeginInvoke和Invoke最后都调用了MarshaledInvoke
        private Object MarshaledInvoke(Control caller, Delegate method, Object[] args, bool synchronous)
        {
            //省略若干。。。。
                UnsafeNativeMethods.PostMessage(new HandleRef(this, Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
                //PostMessage又是什么东东呢
            //省略若干。。。。

            if (synchronous)
            {
                if (!tme.IsCompleted)
                {
                    WaitForWaitHandle(tme.AsyncWaitHandle);
                    //Control 的BeginInvoke 方法和Invoke方法 的区别就在这里了
                    //其实他们的委托都是在拥有Control的线程上执行的也就是UI线程
                    //只是Invoke用到了WaitForWaitHandle 来阻塞调用Invoke的线程 而BeginInvoke没有阻塞
                    //从这个就可以看出Begininvoke的异步 并没有创建新的线程,并不是我们想象中的那种异步!!!
                }
                if (tme.exception != null)
                {
                    throw tme.exception;
                }
                return tme.retVal;
            }
            else
            {
                return (IAsyncResult)tme;
            }
        }

        //PostMessage又是什么东东呢?没错他就是WindowsAPI 哈哈 这下算是挖到低了
        //是一个用来发送消息到窗口消息队列的api函数,但这个方法是非阻塞的。也就是它会马上返回,而不管消息是否真的发送到目的地,也就是调用者不会被阻塞。
        [DllImport(ExternDll.User32, CharSet = CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool PostMessage(HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam);

    }

那么什么是消息队列呢?

Windows GUI程序是基于消息机制的,有个主线程维护着一个消息泵。这个消息泵让windows程序生生不息。

Windows GUI程序的消息循环

Windows程序有个消息队列,窗体上的所有消息是这个队列里面消息的最主要来源。这里的while循环使用了GetMessage()这个方法,这是个阻塞方法,也就是队列为空时方法就会被阻塞,从而这个while循环停止运动,这避免了一个程序把cpu无缘无故地耗尽,让其它程序难以得到响应。当然在某些需要cpu最大限度运动的程序里面就可以使用另外的方法,例如某些3d游戏或者及时战略游戏中,一般会使用PeekMessage()这个方法,它不会被windows阻塞,从而保证整个游戏的流畅和比较高的帧速。

这个主线程维护着整个窗体以及上面的子控件。当它得到一个消息,就会调用DispatchMessage方法派遣消息,这会引起对窗体上的窗口过程的调用。窗口过程里面当然是程序员提供的窗体数据更新代码和其它代码。

【代码5】

public static void Main(string[] args)
{
   Form f = new Form();
   Application.Run(f);
}
//Dotnet窗体程序封装了上述的while循环,这个循环就是通过Application.Run方法启动的。

现在知道我们【代码2】为什么没有执行回调函数了吧,是的我们就只是创建了一个Control而已 并没有让这个这个程序的消息循环跑起来。那现在让我们对代码而做一些改动,看看是不是就可以执行回掉函数了。

【代码6】

class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("我是主线程我的ID是:" + Thread.CurrentThread.ManagedThreadId);
            Application.SetCompatibleTextRenderingDefault(false);
            // TextBox tb = new TextBox(); //单来这个 不行的 , 毕竟到最后还是得依赖消息队列 句柄什么的
            Form form = new Form();
            Button btn = new Button();
            btn.Text = "测试";
            btn.Click += Btn_Click;
            form.Controls.Add(btn);
            Application.Run(form);//模拟UI线程获取SynchronizationContext

            Console.ReadKey();
        }
        private static void Btn_Click(object sender, EventArgs e)
        {
            Thread t1 = new Thread(run);
            t1.Start(SynchronizationContext.Current);
        }
        static void run(object obj)
        {
            Console.WriteLine("我是T1线程我的ID是:" + Thread.CurrentThread.ManagedThreadId);
            SynchronizationContext sc = obj as SynchronizationContext;
            sc.Post(doWork, null);
            Console.WriteLine("执行完成");
        }
        static void doWork(object obj)
        {
            Console.WriteLine("我是t1中使用SynchronizationContext的回调我的ID是:" + Thread.CurrentThread.ManagedThreadId);
        }
    }

果然没错。 回调的线程ID 和主线程的线程ID一致,证明回调是在主线程中执行的。

那么对于两个都不是UI线程的情况呢? 能不能做到跨线程委托的效果呢? 由【代码1】我们知道非UI线程中的SynchronizationContext如果不赋值的话那就是null。

所以在调用前要给SynchronizationContext赋值。

【代码7】

class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("主线程:ID=" + Thread.CurrentThread.ManagedThreadId);
            //设置这个线程的SynchronizationContext
            SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
            Thread t1 = new Thread(Run);
            t1.Start(SynchronizationContext.Current);
            Console.ReadKey();
        }
        static void Run(object obj)
        {
            Console.WriteLine("线程t1:ID=" + Thread.CurrentThread.ManagedThreadId);
            SynchronizationContext sc = obj as SynchronizationContext;
            sc.Send(Callback, null);
            SendOrPostCallback cc = new SendOrPostCallback(Callback);
            Console.WriteLine("线程t1完。");
        }
        static void Callback(object obj)
        {
            Console.WriteLine("回调:ID=" + Thread.CurrentThread.ManagedThreadId);
        }
    }

从执行结果可以看出回调函数打印的线程ID和线程t1的打印的线程ID 一致 ,说明回调并没有在主线程中执行。那这是为啥呢?

看看SynchronizationContext源代码。

【代码8】

public class SynchronizationContext
        {
            //省略若干代码。。。。。。

            //直接调用回调
            public virtual void Send(SendOrPostCallback d, Object state)
            {
                d(state);
            }

            //线程池调用回调
            public virtual void Post(SendOrPostCallback d, Object state)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
            }
            //省略若干代码。。。。。。
        }

看完代码你会发现 它就是什么都没干!!! Send就直接调用了。这也就解释了 回调函数打印的线程ID和线程t1的打印的线程ID 一致。

结语:

SynchronizationContext 只是一个父类而已,从它的Send和Post方法都标记为virtual  可以看出,它仅仅只是作为一个父类而存在,所以直接使用SynchronizationContext是毫无意义的。想要实现一些神奇的功能,你还要自己去重写它。目前MSDN 可以查到的 SynchronizationContext子类有俩个都是和GUI程序有关的看下图

WindowsFormsSynchronizationContext 我们见识过了,DispatcherSynchronizationContext至于这个 是WPF中的 和WindowsFormsSynchronizationContext 功能类似。

参考:

http://www.cnblogs.com/fuchongjundream/p/3939298.html

http://www.cnblogs.com/lzxianren/p/SynchronizationContext.html

完!

时间: 2024-11-06 21:15:02

SynchronizationContext是什么?的相关文章

【C#】SynchronizationContext线程间同步

SynchronizationContext在通讯中充当传输者的角色,实现功能就是一个线程和另外一个线程的通讯. 需要注意的是,不是每个线程都附加SynchronizationContext这个对象,只有UI线程是一直拥有的.故获取SynchronizationContext也只能在UI线程上进行SynchronizationContext context = SynchronizationContext.Current; 那什么时候会用到呢? 在多线程操作时往往需要切回某个线程中去工作,等完成

SynchronizationContext笔记

SynchronizationContext 类是一个基类,可提供不带同步的自由线程上下文. 此类实现的同步模型的目的是使公共语言运行库内部的异步/同步操作能够针对不同的异步模型采取正确的行为.此模型还简化了托管应用程序为在不同的同步环境下正常工作而必须遵循的一些要求.同步模型的提供程序可以扩展此类并为这些方法提供自己的实现. 简而言之就是允许一个线程和另外一个线程进行通讯,SynchronizationContext在通讯中充当传输者的角色.另外这里有个地方需要清楚的,不是每个线程都附加Syn

SynchronizationContext一篇

SynchronizationContext context; 最近写代码用到了这个,特别记录一下. 作用如下: // 摘要: // 提供在各种同步模型中传播同步上下文的基本功能. public class SynchronizationContext { ...... } 来自using System.Threading;这个命名空间,一看很熟悉是线程的大类命名空间. // 摘要: // 获取当前线程的同步上下文. // // 返回结果: // 一个 System.Threading.Sync

理解SynchronizationContext,如何在Winform里面跨线程访问UI控件

SynchronizationContext 类是一个基类,可提供不带同步的自由线程上下文. 此类实现的同步模型的目的是使公共语言运行库内部的异步/同步操作能够针对不同的异步模型采取正确的行为.此模型还简化了托管应用程序为在不同的同步环境下正常工作而必须遵循的一些要求.同步模型的提供程序可以扩展此类并为这些方法提供自己的实现.(来自MSDN)简而言之就是允许一个线程和另外一个线程进行通讯,SynchronizationContext在通讯中充当传输者的角色.另外这里有个地方需要清楚的,不是每个线

线程处理模型 由于 SynchronizationContext 引起的死锁问题解决

由于GUI 应用程序 不能使用线程池的线程更新UI,只能使用 GUI 线程更新,所以在 await 前后需要保证是同一个 GUI 线程 ASP.NET 程序 的线程处理客户端请求的时候,需要假定客户端的语言文化和身份标识等,所以为了保证信息的统一性,await 前后 会用同一个线程来处理... 那么,在 FCL 的 SynchronizationContext 就使用这样的线程模型来解决以上问题.因此偶尔也会带来一些问题:如下, protected void Page_Load(object s

async不得不说的事:SynchronizationContext

待写 ASP.NET环境: internal sealed class AspNetSynchronizationContext : AspNetSynchronizationContextBase { // we move all of the state to a separate field since our CreateCopy() method needs shallow copy semantics private readonly State _state; internal A

SynchronizationContext的研究之一(非WPF及Forms)

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 7 8 namespace SynchronizationTest003 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 SynchronizationContext

探究SynchronizationContext在.Net异步编程中的地位

原文:探究SynchronizationContext在.Net异步编程中的地位 引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式. 相信很多开发者都看到如下异步编程实践原则:   实践原则  说明  例外情况  ①  避免 Async Void  最好使用 async Task 方法而不是 async void 方法  事件处理程序  ②  始终使用 await  不要混合阻塞式代码和

SynchronizationContext(同步上下文)综述

>>返回<C# 并发编程> 1. 概述 2. 同步上下文 的必要性 2.1. ISynchronizeInvoke 的诞生 2.2. SynchronizationContext 的诞生 3. 同步上下文 的概念 4. 同步上下文 的实现 4.1. WinForm 同步上下文 4.2. Dispatcher 同步上下文 4.3. Default 同步上下文 4.4. 上下文捕获和执行 4.5. AspNetSynchronizationContext 5. 同步上下实现类 的注意事