SynchronizationContext是什么?
简单来说,SynchronizationContext
代表代码可能被执行的位置。传递给其Send
或Post
方法的委托将在该位置调用。(Post
是Send
的非阻塞/异步版本。)
每个线程都可以与一个
SynchronizationContext
实例相关联。通过调用
静态的SynchronizationContext.SetSynchronizationContext
方法,可以将运行线程与同步上下文相关联,并且可以通过
SynchronizationContext.Current
属性查询正在运行的线程的当前上下文。
尽管我刚才写的是每个线程都有一个相关联的同步上下文,但是一个 SynchronizationContext
并不一定代表一个具体的线程;它也可以将传递给它的委托转发到数个线程中(例如到 ThreadPool
工作线程),或者(至少在理论上)转发到特定的CPU核心,甚至可以转发到另一台网络主机。你的委托最终在哪个线程上运行取决于所使用的 SynchronizationContext
的类型。
Windows Forms 会在创建第一个窗体的线程上安装一个 WindowsFormsSynchronizationContext
。(这个线程通常称为“UI线程”。) 这种类型的同步上下文会在完全那个线程上调用传递给它的委托。这非常有用,因为 Windows Forms,像许多其他 UI 框架一样,只允许在创建它们的同一线程上操作控件。
如果我在方法中只是写 myTextBox.Text = text;
,有什么区别吗?
The code that you've passed to
ThreadPool.QueueUserWorkItem
will be run on a thread pool worker thread. That is, it will not execute on the thread on which your
myTextBox
was created, so Windows Forms will sooner or later (especially in Release builds) throw an exception, telling you that you may not access
myTextBox
from across another thread.
This is why you have to somehow "switch back" from the worker thread to the "UI thread" (where
myTextBox
was created) before that particular assignment. This is done as follows:
当您仍在UI线程上时,捕获Windows Forms的SynchronizationContext
,并将其存储在一个变量(originalContext
)中以供以后使用。此时,您必须查询SynchronizationContext.Current
; 如果您在传递给ThreadPool.QueueUserWorkItem
的代码中查询它,您可能会得到与线程池工作线程相关联的任何同步上下文。一旦您存储了对Windows Forms上下文的引用,就可以在任何地方和任何时间使用它来“发送”代码到UI线程。
每当您需要操作UI元素(但可能不在UI线程上)时,请通过originalContext
访问Windows Forms的同步上下文,并将将操作UI的代码移交给Send
或Post
。
最后的备注和提示:
同步上下文不能告诉你哪些代码必须在特定位置/上下文中运行,哪些代码可以正常执行而不需要将其传递给“同步上下文”。为了决定这一点,您必须了解您正在编程的框架的规则和要求-在这种情况下是Windows Forms。
因此,请记住Windows Forms的简单规则:不要从创建它们的线程以外的线程访问控件或窗体。如果必须这样做,请使用上述描述的“同步上下文”机制,或者Control.BeginInvoke
(这是一种专门针对Windows Forms进行相同操作的方式)。
如果您正在使用.NET 4.5或更高版本进行编程,则可以通过将明确使用SynchronizationContext
,ThreadPool.QueueUserWorkItem
,control.BeginInvoke
等的代码转换为新的async
/await
关键字和Task Parallel Library (TPL)
,即围绕Task
和Task<TResult>
类的API,使您的生活变得更加轻松。这些将在很大程度上负责捕获UI线程的同步上下文、启动异步操作,然后回到UI线程,以便您可以处理操作的结果。
async
/await
在底层上依赖于SynchronizationContext
。 - stakx - no longer contributing