We in software development love abstractions. We’re rarely happy hardcoding to a particular implementation; rather, when writing higher-level systems, we abstract away the details of a particular implementation so that we can plug in a different implementation later without having to change our higher-level system. This is why we have interfaces, this is why we have abstract classes, this is why we have virtual methods, and so on.
SynchronizationContext is just an abstraction, one that represents a particular environment you want to do some work in. As an example of such an environment, Windows Forms apps have a UI thread (while it’s possible for there to be multiple, for the purposes of this discussion it doesn’t matter), which is where any work that needs to use UI controls needs to happen. For cases where you’re running code on a ThreadPool thread and you need to marshal work back to the UI so that this work can muck with UI controls, Windows Forms provides the Control.BeginInvoke method. You give a delegate to a Control’s BeginInvoke method, and that delegate will be invoked back on the thread with which that control is associated.
So, if I’m writing a component that needs to schedule some work to the ThreadPool and then continue with some work back on the UI thread, I can code my component to use Control.BeginInvoke. But now what if I decide I want to use my component in a WPF app? WPF has the same UI thread constraint that Windows Forms has, but it has a different mechanism for marshaling back to the UI thread: rather than using Control.BeginInvoke on a control associated with the right thread, you use Dispatcher.BeginInvoke (or InvokeAsync) on the Dispatcher instance associated with the right thread.
We now have two different APIs for achieving the same basic operation, so how do I write my component to be agnostic of the UI framework? By using SynchronizationContext. SynchronizationContext provides a virtual Post method; this method simply takes a delegate and runs it wherever, whenever, and however the SynchronizationContext implementation deems fit. Windows Forms provides the WindowsFormSynchronizationContext type which overrides Post to call Control.BeginInvoke. WPF provides the DispatcherSynchronizationContext type which overrides Post to call Dispatcher.BeginInvoke. And so on. As such, I can now code my component to use SynchronizationContext instead of tying it to a specific framework.
If I was writing my component specifically to target Windows Forms, I might implement my go-to-the-ThreadPool-and-then-back-to-the-UI-thread logic something like the following:
public static void DoWork(Control c)
{
ThreadPool.QueueUserWorkItem(delegate
{
… // do work on ThreadPool
c.BeginInvoke(delegate
{
… // do work on UI
});
});
}
If I was instead writing my component to use SynchronizationContext, I might instead write it as:
public static void DoWork(SynchronizationContext sc)
{
ThreadPool.QueueUserWorkItem(delegate
{
… // do work on ThreadPool
sc.Post(delegate
{
… // do work on UI
}, null);
});
}
Of course, it’s annoying (and prohibitive for certain desired programming models) to need to pass around the target context to come back to, so SynchronizationContext provides the Current property, which allows you to discover from the current thread the context that will let you get back to the current environment, if there is one. This allows you to “capture it” (i.e. read the reference from SynchronizationContext.Current and store that reference for later usage):
public static void DoWork()
{
var sc = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate
{
… // do work on ThreadPool
sc.Post(delegate
{
… // do work on the original context
}, null);
});
}

本文深入探讨了SynchronizationContext的概念及其在不同UI框架间的应用。通过示例对比了使用Control.BeginInvoke与SynchronizationContext.Post方法的区别,展示了如何使组件在Windows Forms和WPF等不同环境中保持通用。
914

被折叠的 条评论
为什么被折叠?



