65.9K
CodeProject 正在变化。 阅读更多。
Home

理解 SynchronizationContext(第一部分)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (259投票s)

2008 年 12 月 22 日

CPOL

11分钟阅读

viewsIcon

637491

如何使用 SynchronizationContext

SynchronizationContext – MSDN 让你失望

我不知道为什么,但是关于 .NET Framework 中的这个新类的信息确实不多。MSDN 文档很少提供关于如何使用 SynchronizationContext 的信息。最初,我不得不说,我很难理解这个新类的原因以及如何使用它。在阅读了大量关于这个主题的资料后,我终于理解了这个类的目的以及应该如何使用它。我决定写这篇文章来帮助其他开发人员理解如何使用这个类,以及它能为你做什么和不能为你做什么。(MSDN

使用 SynchronizationContext 将代码从一个线程调度到另一个线程

让我们先了解一些技术要点,以便我们能够展示如何使用这个类。SynchronizationContext 允许一个线程与另一个线程通信。假设你有两个线程,Thread1Thread2。假设 Thread1 正在做一些工作,然后 Thread1 希望在 Thread2 上执行代码。一种可能的方法是向 Thread2 请求其 SynchronizationContext 对象,将其提供给 Thread1,然后 Thread1 可以调用 SynchronizationContext.Send 来在 Thread2 上执行代码。听起来像魔术……嗯,你需要知道一些事情。不是每个线程都有一个 SynchronizationContext 附加到它。一个总是拥有 SynchronizationContext 的线程是 UI 线程。

谁将 SynchronizationContext 放入 UI 线程?有任何猜测吗?放弃?好吧,就是这样,在该线程上创建的第一个控件将 SynchronizationContext 放入该线程中。通常,它是创建的第一个窗体。我怎么知道的?嗯,我试过了。

因为我的代码使用了 SynchronizationContext.Current,所以让我解释一下这个 static 属性给我们带来了什么。SynchronizationContext.Current 允许我们获取附加到当前线程的 SynchronizationContext。让我们在这里明确一点,SynchronizationContext.Current 不是每个 AppDomain 的单例,而是每个线程的。这意味着两个线程在调用 SynchronizationContext.Current 时可以拥有不同的 SynchronizationContext 实例。如果你想知道实际的上下文存储在哪里,它存储在线程数据存储中(正如我之前所说,而不是在 AppDomain 的全局内存空间中)。

好的,让我们看看将 SynchronizationContext 放入我们的 UI 线程中的代码

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

如你所见,有几点需要注意

  • 第一个消息框将指示线程没有附加上下文。这是因为 .NET 甚至不知道这个线程上将发生什么,并且没有运行时类为这个线程初始化同步上下文。
  • 创建窗体后,请注意上下文已设置。Form 类负责此操作。它检查是否存在同步上下文,如果不存在,则将其放置在那里。请记住,在同一线程上上下文始终相同,因此任何 UI 控件都可以访问它。这是因为所有 UI 操作都必须在 UI 线程上运行。更具体地说,创建窗口的线程是能够与窗口通信的线程。在我们的例子中,它是应用程序的主线程。

如何使用?

现在 UI 线程很好地给了我们一个同步上下文,这样我们就可以在 UI 线程下“运行代码”了,我们如何使用它呢?

首先,我们真的必须将代码调度到 UI 线程吗?是的。如果你在 UI 线程以外的线程上运行,则无法更新 UI。想成为英雄并尝试一下吗?你将收到一个异常(在 1.0 版本中,他们没有强制执行异常,它只是使应用程序崩溃,但在 2.0 版本中,会弹出一个令人讨厌的异常)。

公平地说,我得说你不必使用这个类来同步到 UI 线程。你可以使用 InvokeRequired 属性(每个 UI 控件类都有)来查看是否需要调度你的代码。如果你从 InvokeRequired 得到“true”,那么你必须使用 Control.Invoke 将代码调度到 UI 线程。太棒了!为什么要继续阅读呢?嗯,这种技术有一个问题。你必须有一个 Control 才能在其上调用 Invoke。哪个 UI 控件无关紧要,但你需要在非 UI 线程中至少有一个可用的控件引用才能进行这种类型的线程调度。从设计的角度来看,你永远不希望在你的 BI 层中拥有 UI 引用。因此,你可以将所有同步操作留在 UI 类上,并确保 UI 负责调度自己的工作(请参阅我关于 MVP 模式的文章)。然而,这会给 UI 带来更多责任,并使 UI 比我们想要的更智能,我必须说。如果 BI 能够在没有控件或窗体引用的情况下将代码调度到 UI 线程,那将是很好的。

那么,它是如何完成的呢?

简单。创建一个线程,将同步上下文发送给它,并让该线程使用同步对象将代码调度到 UI 线程。让我们看一个例子。

在下面的示例中,我有一个列表框,它由一个工作线程填充。该线程模拟计算,然后写入 UI 列表框。用于更新 UI 的线程是从 mToolStripButtonThreads_Click 事件处理程序启动的。

首先,让我们看看窗体上有哪些内容

    private void InitializeComponent()
    {
        System.ComponentModel.ComponentResourceManager resources =
          new System.ComponentModel.ComponentResourceManager(typeof(Form1));
        this.mListBox = new System.Windows.Forms.ListBox();
        this.toolStrip1 = new System.Windows.Forms.ToolStrip();
        this.mToolStripButtonThreads = new System.Windows.Forms.ToolStripButton();
        this.toolStrip1.SuspendLayout();
        this.SuspendLayout();
        //
        // mListBox
        //
        this.mListBox.Dock = System.Windows.Forms.DockStyle.Fill;
        this.mListBox.FormattingEnabled = true;
        this.mListBox.Location = new System.Drawing.Point(0, 0);
        this.mListBox.Name = "mListBox";
        this.mListBox.Size = new System.Drawing.Size(284, 264);
        this.mListBox.TabIndex = 0;
        //
        // toolStrip1
        //
        this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
        this.mToolStripButtonThreads});
        this.toolStrip1.Location = new System.Drawing.Point(0, 0);
        this.toolStrip1.Name = "toolStrip1";
        this.toolStrip1.Size = new System.Drawing.Size(284, 25);
        this.toolStrip1.TabIndex = 1;
        this.toolStrip1.Text = "toolStrip1";
        //
        // mToolStripButtonThreads
        //
        this.mToolStripButtonThreads.DisplayStyle =
          System.Windows.Forms.ToolStripItemDisplayStyle.Text;
        this.mToolStripButtonThreads.Image = ((System.Drawing.Image)
            (resources.GetObject("mToolStripButtonThreads.Image")));
        this.mToolStripButtonThreads.ImageTransparentColor =
             System.Drawing.Color.Magenta;
        this.mToolStripButtonThreads.Name = "mToolStripButtonThreads";
        this.mToolStripButtonThreads.Size = new System.Drawing.Size(148, 22);
        this.mToolStripButtonThreads.Text = "Press Here to start threads";
        this.mToolStripButtonThreads.Click +=
          new System.EventHandler(this.mToolStripButtonThreads_Click);
        //
        // Form1
        //
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(284, 264);
        this.Controls.Add(this.toolStrip1);
        this.Controls.Add(this.mListBox);
        this.Name = "Form1";
        this.Text = "Form1";
        this.toolStrip1.ResumeLayout(false);
        this.toolStrip1.PerformLayout();
        this.ResumeLayout(false);
        this.PerformLayout();
    }

    #endregion

    private System.Windows.Forms.ListBox mListBox;
    private System.Windows.Forms.ToolStrip toolStrip1;
    private System.Windows.Forms.ToolStripButton mToolStripButtonThreads;
}

现在,让我们看看这个例子

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);
    }
}

让我们回顾一下这段代码。请注意,我记录了每个方法的线程 ID,以便我们稍后可以查看。

例如

// let's see the thread id
int id = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id);

当按下工具栏按钮时,会启动一个线程,其委托指向 Run 方法。但是,请注意我正在向该线程传递状态。我通过调用以下代码传递 UI 线程的同步上下文:

SynchronizationContext uiContext = SynchronizationContext.Current;

因为我正在工具栏按钮的事件处理程序线程上运行,我知道我当前正在 UI 线程上运行,并且通过调用 SynchronizationContext.Current,我将获得 UI 线程的同步上下文。

Run 首先会从它的状态中获取 SynchronizationContext,这样它就能知道如何将代码调度到 UI 线程。

// grab the context from the state
SynchronizationContext uiContext = state as SynchronizationContext;

Run 线程在列表框中写入 1000 行。怎么做到的?首先,它使用了 SynchronizationContext 上的 Send 方法

public virtual void Send(SendOrPostCallback d, object state);

调用 SynchronizationContext.Send 需要两个参数,一个指向方法的委托和一个 state 对象。在我们的示例中……

uiContext.Send(UpdateUI, "line " + i.ToString());

... UpdateUI 是我们为委托提供的值,而 state 包含我们想要添加到 listboxstringUpdateUI 中的代码应该在 UI 线程上运行,而不是在调用线程上运行。

private void UpdateUI(object state)
{
    int id = Thread.CurrentThread.ManagedThreadId;
    Trace.WriteLine("UpdateUI thread:" + id);
    string text = state as string;
    mListBox.Items.Add(text);
}

请注意,这段代码直接在 UI 线程上运行。没有检查 InvokerRequired,因为我知道它在 UI 线程上,这是因为它与 UI SynchronizationContextSend 方法一起使用。

让我们看看线程 ID,看看它是否有意义

mToolStripButtonThreads_Click thread: 10
Run thread: 3
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
... (x1000 times)

这意味着 UI 线程是 10,工作线程 (Run) 是 3,当我们更新 UI 时,请注意我们再次在线程 ID 10 上(UI 线程)。所以,一切都按预期工作。

错误处理

非常好,我们能够将代码调度到 UI 线程中,但是当我们调度​​的代码抛出异常时会发生什么?谁负责捕获它?UI 线程还是工作线程?

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");
}

我修改了代码,以便 UpdateUI 方法抛出异常

throw new Exception("Boom");

此外,我修改了 Run 方法,在 Send 方法周围放置了一个 try/catch

try
{
    uiContext.Send(UpdateUI, "line " + i.ToString());
}
catch (Exception e)
{
    Trace.WriteLine(e.Message);
}

当运行这段代码时,我注意到异常是在 Run 线程中捕获的,而不是在 UI 线程中。这很有趣,因为你可能期望异常会使 UI 线程崩溃,考虑到没有类在 UI 线程上捕获异常。

因此,Send 方法正在做一些小魔法;它以阻塞方式执行我们的代码,并在执行期间报告任何异常。

Send 与 Post

使用 Send 只是你可以用来在 UI 线程上调度代码的两种可能方法之一。还有另一种方法叫做 Post。有什么区别?很大!

也许是时候更详细地查看这个类了,所以让我们回顾一下 SynchronizationContext 的接口

// Summary:
//     Provides the basic functionality for propagating a synchronization context
//     in various synchronization models.
public class SynchronizationContext
{
    // Summary:
    //     Creates a new instance of the System.Threading.SynchronizationContext class.
    public SynchronizationContext();

    // Summary:
    //     Gets the synchronization context for the current thread.
    //
    // Returns:
    //     A System.Threading.SynchronizationContext object representing the current
    //     synchronization context.
    public static SynchronizationContext Current { get; }

    // Summary:
    //     When overridden in a derived class, creates a copy of the synchronization
    //     context.
    //
    // Returns:
    //     A new System.Threading.SynchronizationContext object.
    public virtual SynchronizationContext CreateCopy();
    //
    // Summary:
    //     Determines if wait notification is required.
    //
    // Returns:
    //     true if wait notification is required; otherwise, false.
    public bool IsWaitNotificationRequired();
    //
    // Summary:
    //     When overridden in a derived class, responds to the notification that an
    //     operation has completed.
    public virtual void OperationCompleted();
    //
    // Summary:
    //     When overridden in a derived class, responds to the notification that an
    //     operation has started.
    public virtual void OperationStarted();
    //
    // Summary:
    //     When overridden in a derived class, dispatches an asynchronous message to
    //     a synchronization context.
    //
    // Parameters:
    //   d:
    //     The System.Threading.SendOrPostCallback delegate to call.
    //
    //   state:
    //     The object passed to the delegate.
    public virtual void Post(SendOrPostCallback d, object state);
    //
    // Summary:
    //     When overridden in a derived class, dispatches a synchronous message to a
    //     synchronization context.
    //
    // Parameters:
    //   d:
    //     The System.Threading.SendOrPostCallback delegate to call.
    //
    //   state:
    //     The object passed to the delegate.
    public virtual void Send(SendOrPostCallback d, object state);
    //
    // Summary:
    //     Sets the current synchronization context.
    //
    // Parameters:
    //   syncContext:
    //     The System.Threading.SynchronizationContext object to be set.
    public static void SetSynchronizationContext(SynchronizationContext syncContext);
    //
    // Summary:
    //     Sets notification that wait notification is required and prepares the callback
    //     method so it can be called more reliably when a wait occurs.
    protected void SetWaitNotificationRequired();
    //
    // Summary:
    //     Waits for any or all the elements in the specified array to receive a signal.
    //
    // Parameters:
    //   waitHandles:
    //     An array of type System.IntPtr that contains the native operating system
    //     handles.
    //
    //   waitAll:
    //     true to wait for all handles; false to wait for any handle.
    //
    //   millisecondsTimeout:
    //     The number of milliseconds to wait, or System.Threading.Timeout.Infinite
    //     (-1) to wait indefinitely.
    //
    // Returns:
    //     The array index of the object that satisfied the wait.
    [PrePrepareMethod]
    [CLSCompliant(false)]
    public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
    //
    // Summary:
    //     Helper function that waits for any or all the elements in the specified array
    //     to receive a signal.
    //
    // Parameters:
    //   waitHandles:
    //     An array of type System.IntPtr that contains the native operating system
    //     handles.
    //
    //   waitAll:
    //     true to wait for all handles; false to wait for any handle.
    //
    //   millisecondsTimeout:
    //     The number of milliseconds to wait, or System.Threading.Timeout.Infinite
    //     (-1) to wait indefinitely.
    //
    // Returns:
    //     The array index of the object that satisfied the wait.
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [PrePrepareMethod]
    [CLSCompliant(false)]
    protected static int WaitHelper(IntPtr[] waitHandles,
                     bool waitAll, int millisecondsTimeout);
}

请注意 Post 方法的注释

//
// Summary:
//     When overridden in a derived class, dispatches an asynchronous message to
//     a synchronization context.
//
// Parameters:
//   d:
//     The System.Threading.SendOrPostCallback delegate to call.
//
//   state:
//     The object passed to the delegate.
public virtual void Post(SendOrPostCallback d, object state);

这里的关键词是 **异步**。这意味着 Post 不会等待委托的执行完成。Post 会“一劳永逸”地执行委托中的代码。这也意味着你无法像使用 Send 方法那样捕获异常。假设抛出了一个异常,将由 UI 线程捕获;未处理的异常将终止 UI 线程。

然而,无论是 Post 还是 Send,委托的执行始终在正确的线程上运行。只需用 Post 替换 Send,在 UI 线程上执行时,你仍然会获得正确的线程 ID。

那么现在,我可以使用 SynchronizationContext 同步我想要的任何线程,对吗?不对!

此时,你可能会尝试将 SynchronizationContext 与任何线程一起使用。然而,你很快就会发现你的线程在使用 SynchronizationContext.Current 时没有 SynchronizationContext,并且它总是返回 null。你说没什么大不了的,如果不存在,你就简单地创建一个 SynchronizationContext。很简单。但是,它实际上不起作用。

让我们看一个类似于我们用于 UI 线程的程序

class Program
{
    private static SynchronizationContext mT1 = null;

    static void Main(string[] args)
    {
        // log the thread id
        int id = Thread.CurrentThread.ManagedThreadId;
        Console.WriteLine("Main thread is " + id);

        // create a sync context for this thread
        var context = new SynchronizationContext();
        // set this context for this thread.
        SynchronizationContext.SetSynchronizationContext(context);

        // create a thread, and pass it the main sync context.
        Thread t1 = new Thread(new ParameterizedThreadStart(Run1));
        t1.Start(SynchronizationContext.Current);
        Console.ReadLine();
    }

    static private void Run1(object state)
    {
        int id = Thread.CurrentThread.ManagedThreadId;
        Console.WriteLine("Run1 Thread ID: " + id);

        // grab  the sync context that main has set
        var context = state as SynchronizationContext;

        // call the sync context of main, expecting
        // the following code to run on the main thread
        // but it will not.
        context.Send(DoWork, null);

        while (true)
            Thread.Sleep(10000000);
    }

    static void DoWork(object state)
    {
        int id = Thread.CurrentThread.ManagedThreadId;
        Console.WriteLine("DoWork Thread ID:" + id);
    }
}

这个简单的控制台应用程序是你绝不应该在家中做的事情。这个程序不起作用,它只是为了证明一个观点。注意我正在主控制台线程上设置一个同步上下文。我只是简单地创建了一个对象的新实例。然后,我将其设置到我当前的线程。这类似于当窗体创建时 UI 线程所做的事情(不完全是,但我稍后会解释)。然后,我创建了一个线程 Run1,并将主线程的上下文传递给它。当我尝试调用 Send 时,根据我的跟踪,我注意到 Send 是在 Run1 线程上调用的,而不是像我们可能期望的那样在主线程上调用的。这是输出

Main thread is 10
Run1 Thread ID: 11
DoWork Thread ID:11

请注意,DoWork 在线程 11 上执行,与 Run1 相同。没有太多 SynchronizationContext 进入主线程。为什么?发生了什么?好吧……这就是你意识到生活中没有免费午餐的时候。线程不能只是在它们之间切换上下文,它们必须内置一个基础设施才能这样做。例如,UI 线程使用消息泵,并且在其 SynchronizationContext 中,它利用消息泵同步到 UI 线程。

因此,UI 线程有自己的 SynchronizationContext 类,但它是一个派生自 SynchronizationContext 的类,它被称为 System.Windows.Forms.WindowsFormsSynchronizationContext。现在,这个类与简单的普通 SynchronizationContext 有非常不同的实现。UI 版本重写了 PostSend 方法,并提供了这些方法的“消息泵”版本(我试图获取这个类的源代码,但我没有找到)。那么,普通的 SynchronizationContext 做了什么呢?

不知何故,我成功获得了 SynchronizationContext 的源代码,就在这里:在这里找到的

(我已删除属性,并进行了一些小格式调整,以便代码适合页面。)

namespace System.Threading
{
    using Microsoft.Win32.SafeHandles;
    using System.Security.Permissions;
    using System.Runtime.InteropServices;
    using System.Runtime.CompilerServices;
    using System.Runtime.ConstrainedExecution;
    using System.Reflection;

    internal struct SynchronizationContextSwitcher : IDisposable
    {
        internal SynchronizationContext savedSC;
        internal SynchronizationContext currSC;
        internal ExecutionContext _ec;

        public override bool Equals(Object obj)
        {
            if (obj == null || !(obj is SynchronizationContextSwitcher))
                return false;
            SynchronizationContextSwitcher sw = (SynchronizationContextSwitcher)obj;
            return (this.savedSC == sw.savedSC &&
                    this.currSC == sw.currSC && this._ec == sw._ec);
        }

        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }

        public static bool operator ==(SynchronizationContextSwitcher c1,
                                       SynchronizationContextSwitcher c2)
        {
            return c1.Equals(c2);
        }

        public static bool operator !=(SynchronizationContextSwitcher c1,
                                       SynchronizationContextSwitcher c2)
        {
            return !c1.Equals(c2);
        }

        void IDisposable.Dispose()
        {
            Undo();
        }

        internal bool UndoNoThrow()
        {
            if (_ec  == null)
            {
                return true;
            }

            try
            {
                Undo();
            }
            catch
            {
                return false;
            }
            return true;
        }

        public void Undo()
        {
            if (_ec  == null)
            {
                return;
            }

            ExecutionContext  executionContext =
              Thread.CurrentThread.GetExecutionContextNoCreate();
            if (_ec != executionContext)
            {
                throw new InvalidOperationException(Environment.GetResourceString(
                          "InvalidOperation_SwitcherCtxMismatch"));
            }
            if (currSC != _ec.SynchronizationContext)
            {
                throw new InvalidOperationException(Environment.GetResourceString(
                          "InvalidOperation_SwitcherCtxMismatch"));
            }
            BCLDebug.Assert(executionContext != null, " ExecutionContext can't be null");
            // restore the Saved Sync context as current
            executionContext.SynchronizationContext = savedSC;
            // can't reuse this anymore
            _ec = null;
        }
    }

    public delegate void SendOrPostCallback(Object state);

    [Flags]
    enum SynchronizationContextProperties
    {
        None = 0,
        RequireWaitNotification = 0x1
    };

    public class SynchronizationContext
    {
        SynchronizationContextProperties _props = SynchronizationContextProperties.None;

        public SynchronizationContext()
        {
        }

        // protected so that only the derived sync
        // context class can enable these flags
        protected void SetWaitNotificationRequired()
        {
            // Prepare the method so that it can be called
            // in a reliable fashion when a wait is needed.
            // This will obviously only make the Wait reliable
            // if the Wait method is itself reliable. The only thing
            // preparing the method here does is to ensure there
            // is no failure point before the method execution begins.

            RuntimeHelpers.PrepareDelegate(new WaitDelegate(this.Wait));
            _props |= SynchronizationContextProperties.RequireWaitNotification;
        }

        public bool IsWaitNotificationRequired()
        {
            return ((_props &
              SynchronizationContextProperties.RequireWaitNotification) != 0);
        }

        public virtual void Send(SendOrPostCallback d, Object state)
        {
            d(state);
        }

        public virtual void Post(SendOrPostCallback d, Object state)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
        }

        public virtual void OperationStarted()
        {
        }

        public virtual void OperationCompleted()
        {
        }

        // Method called when the CLR does a wait operation
        public virtual int Wait(IntPtr[] waitHandles,
                       bool waitAll, int millisecondsTimeout)
        {
            return WaitHelper(waitHandles, waitAll, millisecondsTimeout);
        }

        // Static helper to which the above method
        // can delegate to in order to get the default
        // COM behavior.
        protected static extern int WaitHelper(IntPtr[] waitHandles,
                         bool waitAll, int millisecondsTimeout);

        // set SynchronizationContext on the current thread
        public static void SetSynchronizationContext(SynchronizationContext syncContext)
        {
            SetSynchronizationContext(syncContext,
              Thread.CurrentThread.ExecutionContext.SynchronizationContext);
        }

        internal static SynchronizationContextSwitcher
          SetSynchronizationContext(SynchronizationContext syncContext,
          SynchronizationContext prevSyncContext)
        {
            // get current execution context
            ExecutionContext ec = Thread.CurrentThread.ExecutionContext;
            // create a switcher
            SynchronizationContextSwitcher scsw = new SynchronizationContextSwitcher();

            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                // attach the switcher to the exec context
                scsw._ec = ec;
                // save the current sync context using the passed in value
                scsw.savedSC = prevSyncContext;
                // save the new sync context also
                scsw.currSC = syncContext;
                // update the current sync context to the new context
                ec.SynchronizationContext = syncContext;
            }
            catch
            {
                // Any exception means we just restore the old SyncCtx
                scsw.UndoNoThrow(); //No exception will be thrown in this Undo()
                throw;
            }
            // return switcher
            return scsw;
        }

        // Get the current SynchronizationContext on the current thread
        public static SynchronizationContext Current
        {
            get
            {
                ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate();
                if (ec != null)
                    return ec.SynchronizationContext;
                return null;
            }
        }

        // helper to Clone this SynchronizationContext,
        public virtual SynchronizationContext CreateCopy()
        {
            // the CLR dummy has an empty clone function - no member data
            return new SynchronizationContext();
        }

        private static int InvokeWaitMethodHelper(SynchronizationContext syncContext,
            IntPtr[] waitHandles,
            bool waitAll,
            int millisecondsTimeout)
        {
            return syncContext.Wait(waitHandles, waitAll, millisecondsTimeout);
        }
    }
}

看看 SendPost 的实现……

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 只是在调用线程上调用委托(没有任何线程切换),Post 做同样的事情,但只是使用 ThreadPool 以异步方式完成。在我看来,这个类应该是 abstract 的。这个类的默认实现令人困惑且无用。这是我决定写这篇文章的两个原因之一。

结论

希望你现在对这个类有了更多的了解,并且理解了如何使用它。在 .NET 中,我发现了两个提供自定义同步的类。一个用于 WinForms 线程上下文,一个用于 WPF 线程上下文。我相信还有更多,但这些是我目前发现的。正如我向你展示的,该类的默认实现什么也没有做来将代码从一个线程切换到另一个线程。这仅仅是因为线程默认没有这种机制。另一方面,UI 线程有消息泵和 Windows API,例如 SendMessagePostMessage,我确信在将代码调度到 UI 线程时使用了这些 API。

然而,这不应该是这个类的终点。你可以创建自己的 SynchronizationContext,这真的很简单。事实上,我不得不写一个。在我的工作中,我们需要所有基于 COM 的调用都在 STA 线程上执行。然而,我们的应用程序正在使用线程池和 WCF,将代码调度到 STA 线程并不简单。因此,我决定编写我自己的 SynchronizationContext 版本,称为 StaSynchronizationContext。我将在本文的第二部分展示我是如何做到的。

祝你 .NET 开发愉快。

理解 SynchronizationContext(第一部分) - CodeProject - 代码之家
© . All rights reserved.