C# SynchronizationContext 有什么作用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18097471/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 11:16:30  来源:igfitidea点击:

What does SynchronizationContext do?

c#.netmultithreading

提问by cloudyFan

In the book Programming C#, it has some sample code about SynchronizationContext:

在《Programming C#》一书中,它有一些关于以下内容的示例代码SynchronizationContext

SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
    string text = File.ReadAllText(@"c:\temp\log.txt");
    originalContext.Post(delegate {
        myTextBox.Text = text;
    }, null);
});

I'm a beginner in threads, so please answer in detail. First, I don't know what does context mean, what does the program save in the originalContext? And when the Postmethod is fired, what will the UI thread do?
If I ask some silly things, please correct me, thanks!

我是线程的初学者,所以请详细回答。首先,我不知道上下文是什么意思,程序保存在originalContext? 当Post方法被触发时,UI 线程会做什么?
如果我问了一些愚蠢的事情,请纠正我,谢谢!

EDIT: For example, what if I just write myTextBox.Text = text;in the method, what's the difference?

编辑:例如,如果我只myTextBox.Text = text;在方法中写入怎么办,有什么区别?

回答by Erik Funkenbusch

The purpose of the synchronization context here is to make sure that myTextbox.Text = text;gets called on the main UI thread.

这里同步上下文的目的是确保myTextbox.Text = text;在主 UI 线程上调用它。

Windows requires that GUI controls be accessed only by the thread they were created with. If you try assign the text in a background thread without first synchronizing (through any of several means, such as this or the Invoke pattern) then an exception will be thrown.

Windows 要求 GUI 控件只能由创建它们的线程访问。如果您尝试在后台线程中分配文本而不先同步(通过多种方式中的任何一种,例如 this 或 Invoke 模式),则会引发异常。

What this does is save the synchronization context prior to creating the background thread, then the background thread uses the context.Post method execute the GUI code.

这样做是在创建后台线程之前保存同步上下文,然后后台线程使用 context.Post 方法执行 GUI 代码。

Yes, the code you've shown is basically useless. Why create a background thread, only to immediately need to go back to the main UI thread? It's just an example.

是的,您显示的代码基本上没用。为什么创建后台线程,只需要立即返回主UI线程?这只是一个例子。

回答by Hans Passant

It stores the synchronization provider, a class derived from SynchronizationContext. In this case that will probably be an instance of WindowsFormsSynchronizationContext. That class uses the Control.Invoke() and Control.BeginInvoke() methods to implement the Send() and Post() methods. Or it can be DispatcherSynchronizationContext, it uses Dispatcher.Invoke() and BeginInvoke(). In a Winforms or WPF app, that provider is automatically installed as soon as you create a window.

它存储同步提供者,一个从 SynchronizationContext 派生的类。在这种情况下,这可能是 WindowsFormsSynchronizationContext 的一个实例。该类使用 Control.Invoke() 和 Control.BeginInvoke() 方法来实现 Send() 和 Post() 方法。或者它可以是 DispatcherSynchronizationContext,它使用 Dispatcher.Invoke() 和 BeginInvoke()。在 Winforms 或 WPF 应用程序中,创建窗口后会立即自动安装该提供程序。

When you run code on another thread, like the thread-pool thread used in the snippet, then you have to be careful that you don't directly use objects that are thread-unsafe. Like any user interface object, you must update the TextBox.Text property from the thread that created the TextBox. The Post() method ensures that the delegate target runs on that thread.

当你在另一个线程上运行代码时,比如代码片段中使用的线程池线程,那么你必须小心不要直接使用线程不安全的对象。与任何用户界面对象一样,您必须从创建 TextBox 的线程更新 TextBox.Text 属性。Post() 方法确保委托目标在该线程上运行。

Beware that this snippet is a bit dangerous, it will only work correctly when you call it from the UI thread. SynchronizationContext.Current has different values in different threads. Only the UI thread has a usable value. And is the reason the code had to copy it. A more readable and safer way to do it, in a Winforms app:

请注意,此代码段有点危险,只有当您从 UI 线程调用它时,它才能正常工作。SynchronizationContext.Current 在不同线程中具有不同的值。只有 UI 线程具有可用值。这就是代码必须复制它的原因。在 Winforms 应用程序中,一种更易读、更安全的方法:

    ThreadPool.QueueUserWorkItem(delegate {
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.BeginInvoke(new Action(() => {
            myTextBox.Text = text;
        }));
    });

Which has the advantage that it works when called from anythread. The advantage of using SynchronizationContext.Current is that it still works whether the code is used in Winforms or WPF, it matters in a library. This is certainly nota good example of such code, you always know what kind of TextBox you have here so you always know whether to use Control.BeginInvoke or Dispatcher.BeginInvoke. Actually using SynchronizationContext.Current is not that common.

其优点是从任何线程调用时都可以工作。使用 SynchronizationContext.Current 的优点是无论代码是在 Winforms 还是 WPF 中使用它仍然有效,它在库中很重要。这当然不是此类代码的一个很好的例子,你总是知道这里有什么样的 TextBox,所以你总是知道是使用 Control.BeginInvoke 还是 Dispatcher.BeginInvoke。实际上使用 SynchronizationContext.Current 并不常见。

The book is trying to teach you about threading, so using this flawed example is okayish. In real life, in the few cases where you mightconsider using SynchronizationContext.Current, you'd still leave it up to C#'s async/await keywords or TaskScheduler.FromCurrentSynchronizationContext() to do it for you. But do note that they still misbehave the way the snippet does when you use them on the wrong thread, for the exact same reason. A very common question around here, the extra level of abstraction is useful but makes it harder to figure out why they don't work correctly. Hopefully the book also tells you when not to use it :)

这本书试图教你线程,所以使用这个有缺陷的例子是可以的。在现实生活中,在您可能考虑使用 SynchronizationContext.Current的少数情况下,您仍会将其留给 C# 的 async/await 关键字或 TaskScheduler.FromCurrentSynchronizationContext() 来为您完成。但请注意,出于完全相同的原因,当您在错误的线程上使用它们时,它们的行为仍然与代码段的行为方式相同。这里有一个非常常见的问题,额外的抽象级别很有用,但会让人更难弄清楚为什么它们不能正常工作。希望这本书也告诉你什么时候不使用它:)

回答by stakx - no longer contributing

What does SynchronizationContext do?

SynchronizationContext 有什么作用?

Simply put, SynchronizationContextrepresents a location "where" code might be executed. Delegates that are passed to its Sendor Postmethodwill then be invoked in that location. (Postis the non-blocking / asynchronous version of Send.)

简而言之,SynchronizationContext表示可能会执行代码的“位置”。然后将在该位置调用传递给其SendPost方法的委托。(Post是 的非阻塞/异步版本Send。)

Every thread can have a SynchronizationContextinstance associated with it. The running thread can be associated with a synchronization context by calling the static SynchronizationContext.SetSynchronizationContextmethod, and the current context of the running thread can be queried via the SynchronizationContext.Currentproperty.

每个线程都可以有一个SynchronizationContext与之关联的实例。运行线程可以通过调用静态SynchronizationContext.SetSynchronizationContext方法关联一个同步上下文,并且可以通过SynchronizationContext.Current属性查询运行线程的当前上下文。

Despite what I just wrote (each thread having an associated synchronization context), a SynchronizationContextdoes not necessarily represent a specific thread; it can also forward invocation of the delegates passed to it to any of severalthreads (e.g. to a ThreadPoolworker thread), or (at least in theory) to a specific CPU core, or even to another network host. Where your delegates end up running is dependent on the type of SynchronizationContextused.

尽管我刚刚写了什么(每个线程都有一个关联的同步上下文),但 aSynchronizationContext不一定代表特定的线程;它还可以将传递给它的委托的调用转发给几个线程中的任何一个(例如,ThreadPool工作线程),或(至少在理论上)特定的CPU 内核,甚至另一个网络主机。您的代表最终运行的位置取决于使用的类型SynchronizationContext

Windows Forms will install a WindowsFormsSynchronizationContexton the thread on which the first form is created. (This thread is commonly called "the UI thread".) This type of synchronization context invokes the delegates passed to it on exactly that thread. This is very useful since Windows Forms, like many other UI frameworks, only permits manipulation of controls on the same thread on which they were created.

Windows 窗体将WindowsFormsSynchronizationContext在创建第一个窗体的线程上安装。(此线程通常称为“UI 线程”。)这种类型的同步上下文会调用在该线程上传递给它的委托。这非常有用,因为 Windows 窗体与许多其他 UI 框架一样,只允许在创建它们的同一线程上操作控件。

What if I just write myTextBox.Text = text;in the method, what's the difference?

如果我只是myTextBox.Text = text;在方法中写,那有什么区别呢?

The code that you've passed to ThreadPool.QueueUserWorkItemwill be run on a thread pool worker thread. That is, it will not execute on the thread on which your myTextBoxwas created, so Windows Forms will sooner or later (especially in Release builds) throw an exception, telling you that you may not access myTextBoxfrom across another thread.

您传递给的代码ThreadPool.QueueUserWorkItem将在线程池工作线程上运行。也就是说,它不会在myTextBox创建您的线程上执行,因此 Windows 窗体迟早会(尤其是在发布版本中)抛出异常,告诉您您可能无法myTextBox从另一个线程访问。

This is why you have to somehow "switch back" from the worker thread to the "UI thread" (where myTextBoxwas created) before that particular assignment. This is done as follows:

这就是为什么您必须myTextBox在该特定分配之前以某种方式从工作线程“切换回”到“UI 线程”(创建位置)。这是按如下方式完成的:

  1. While you are still on the UI thread, capture Windows Forms' SynchronizationContextthere, and store a reference to it in a variable (originalContext) for later use. You must query SynchronizationContext.Currentat this point; if you queried it inside the code passed to ThreadPool.QueueUserWorkItem, you might get whatever synchronization context is associated with the thread pool's worker thread. Once you have stored a reference to Windows Forms' context, you can use it anywhere and at any time to "send" code to the UI thread.

  2. Whenever you need to manipulate a UI element (but are not, or might not be, on the UI thread anymore), access Windows Forms' synchronization context via originalContext, and hand off the code that will manipulate the UI to either Sendor Post.

  1. 当您仍然在 UI 线程上时,在SynchronizationContext那里捕获 Windows 窗体,并将对其的引用存储在变量 ( originalContext) 中以备后用。此时您必须查询SynchronizationContext.Current;如果您在传递给 的代码中查询它ThreadPool.QueueUserWorkItem,您可能会获得与线程池的工作线程相关联的任何同步上下文。一旦您存储了对 Windows 窗体上下文的引用,您就可以随时随地使用它来将代码“发送”到 UI 线程。

  2. 每当您需要操作 UI 元素时(但不再或可能不再在 UI 线程上),请通过 访问 Windows 窗体的同步上下文originalContext,并将操作 UI 的代码移交给SendPost



Final remarks and hints:

最后的评论和提示:

  • What synchronization contexts won'tdo for you is telling you which code must run in a specific location / context, and which code can just be executed normally, without passing it to a SynchronizationContext. In order to decide that, you must know the rules and requirements of the framework you're programming against — Windows Forms in this case.

    So remember this simple rule for Windows Forms: DO NOT access controls or forms from a thread other than the one that created them. If you must do this, use the SynchronizationContextmechanism as described above, or Control.BeginInvoke(which is a Windows Forms-specific way of doing exactly the same thing).

  • If you're programming against .NET 4.5 or later, you can make your life much easier by converting your code that explicitly uses SynchronizationContext, ThreadPool.QueueUserWorkItem, control.BeginInvoke, etc. over to the new async/ awaitkeywordsand the Task Parallel Library (TPL), i.e. the API surrounding the Taskand ` class">Task<TResult>classes. These will, to a very high degree, take care of capturing the UI thread's synchronization context, starting an asynchronous operation, then getting back onto the UI thread so you can process the operation's result.

  • 同步上下文不会告诉您哪些代码必须在特定位置/上下文中运行,哪些代码可以正常执行,而无需将其传递给SynchronizationContext. 为了做出决定,您必须了解您编程所针对的框架的规则和要求 — 在本例中为 Windows 窗体。

    因此,请记住 Windows 窗体的这条简单规则:不要从创建它们的线程以外的线程访问控件或窗体。如果您必须这样做,请使用上述SynchronizationContext机制,或者Control.BeginInvoke(这是执行完全相同操作的特定于 Windows 窗体的方式)。

  • 如果你对编程.NET 4.5或更高版本,可以通过转换代码是明确地让你的生活更容易使用SynchronizationContextThreadPool.QueueUserWorkItemcontrol.BeginInvoke等转移到新async/await关键字任务并行库(TPL) ,即围绕API在Task` 类的 MSDN 参考页">Task<TResult>类。这些将在很大程度上负责捕获 UI 线程的同步上下文,启动异步操作,然后返回到 UI 线程,以便您可以处理操作的结果。

回答by noseratio

I'd like to add to other answers, SynchronizationContext.Postjust queues a callback for later execution on the target thread (normally during the next cycle of the target thread's message loop), and then execution continues on the calling thread. On the other hand, SynchronizationContext.Sendtries to execute the callback on the target thread immediately, which blocks the calling thread and may result in deadlock. In both cases, there is a possibility for code reentrancy (entering a class method on the same thread of execution before the previous call to the same method has returned).

我想添加其他答案,SynchronizationContext.Post只是将回调排队以便稍后在目标线程上执行(通常在目标线程的消息循环的下一个循环中),然后在调用线程上继续执行。另一方面,SynchronizationContext.Send试图立即在目标线程上执行回调,这会阻塞调用线程并可能导致死锁。在这两种情况下,都存在代码重入的可能性(在先前对同一方法的调用返回之前,在同一执行线程上进入类方法)。

If you're familiar with Win32 programming model, a very close analogy would be PostMessageand SendMessageAPIs, which you can call to dispatch a message from a thread different from the target window's one.

如果你熟悉Win32编程模型,一个非常密切的类比是PostMessageSendMessageAPI,这些API,你可以打电话从目标窗口的一个线程分派不同的消息。

Here is a very good explanation of what synchronization contexts are: It's All About the SynchronizationContext.

这里有一个关于什么是同步上下文的很好的解释: 它是关于 SynchronizationContext 的

回答by Bigeyes

To the Source

到源头

Every thread has a context associated with it -- this is also known as the "current" context -- and these contexts can be shared across threads. The ExecutionContext contains relevant metadata of the current environment or context in which the program is in execution. The SynchronizationContext represents an abstraction -- it denotes the location where your application's code is executed.

A SynchronizationContext enables you to queue a task onto another context. Note that every thread can have its own SynchronizatonContext.

每个线程都有一个与之关联的上下文——这也称为“当前”上下文——并且这些上下文可以跨线程共享。ExecutionContext 包含程序正在执行的当前环境或上下文的相关元数据。SynchronizationContext 代表一个抽象——它表示应用程序代码执行的位置。

SynchronizationContext 使您能够将任务排队到另一个上下文中。请注意,每个线程都可以有自己的 SynchronizatonContext。

For example: Suppose you have two threads, Thread1 and Thread2. Say, Thread1 is doing some work, and then Thread1 wishes to execute code on Thread2. One possible way to do it is to ask Thread2 for its SynchronizationContext object, give it to Thread1, and then Thread1 can call SynchronizationContext.Send to execute the code on Thread2.

例如:假设您有两个线程,Thread1 和 Thread2。比如说,Thread1 正在做一些工作,然后 Thread1 希望在 Thread2 上执行代码。一种可能的方法是向 Thread2 请求其 SynchronizationContext 对象,将其提供给 Thread1,然后 Thread1 可以调用 SynchronizationContext.Send 在 Thread2 上执行代码。

回答by loneshark99

This example is from Linqpad examples from Joseph Albahari but it really helps in understanding what Synchronization context does.

这个例子来自 Joseph Albahari 的 Linqpad 例子,但它确实有助于理解同步上下文的作用。

void WaitForTwoSecondsAsync (Action continuation)
{
    continuation.Dump();
    var syncContext = AsyncOperationManager.SynchronizationContext;
    new Timer (_ => syncContext.Post (o => continuation(), _)).Change (2000, -1);
}

void Main()
{
    Util.CreateSynchronizationContext();
    ("Waiting on thread " + Thread.CurrentThread.ManagedThreadId).Dump();
    for (int i = 0; i < 10; i++)
        WaitForTwoSecondsAsync (() => ("Done on thread " + Thread.CurrentThread.ManagedThreadId).Dump());
}

回答by Marc2001

SynchronizationContext provides us a way to update a UI from a different thread (synchronously via the Send method or asynchronously via the Post method).

SynchronizationContext 为我们提供了一种从不同线程更新 UI 的方法(通过 Send 方法同步或通过 Post 方法异步)。

Take a look at the following example:

看看下面的例子:

    private void SynchronizationContext SyncContext = SynchronizationContext.Current;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(Work1);
        thread.Start(SyncContext);
    }

    private void Work1(object state)
    {
        SynchronizationContext syncContext = state as SynchronizationContext;
        syncContext.Post(UpdateTextBox, syncContext);
    }

    private void UpdateTextBox(object state)
    {
        Thread.Sleep(1000);
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.Text = text;
    }

SynchronizationContext.Current will return the UI thread's sync context. How do I know this? At the start of every form or WPF app, the context will be set on the UI thread. If you create a WPF app and run my example, you'll see that when you click the button, it sleeps for roughly 1 second, then it will show the file's content. You might expect it won't because the caller of UpdateTextBox method (which is Work1) is a method passed to a Thread, therefore it should sleep that thread not the main UI thread, NOPE! Even though Work1 method is passed to a thread, notice that it also accepts an object which is the SyncContext. If you look at it, you'll see that the UpdateTextBox method is executed through the syncContext.Post method and not the Work1 method. Take a look at the following:

SynchronizationContext.Current 将返回 UI 线程的同步上下文。我怎么知道这个?在每个表单或 WPF 应用程序开始时,上下文将在 UI 线程上设置。如果您创建一个 WPF 应用程序并运行我的示例,您会看到当您单击按钮时,它会休眠大约 1 秒钟,然后它将显示文件的内容。您可能期望它不会,因为 UpdateTextBox 方法(即 Work1)的调用者是传递给线程的方法,因此它应该休眠该线程而不是主 UI 线程,NOPE!即使 Work1 方法被传递给一个线程,请注意它也接受一个对象,它是 SyncContext。如果您查看它,您会看到 UpdateTextBox 方法是通过 syncContext.Post 方法而不是 Work1 方法执行的。看看以下内容:

private void Button_Click(object sender, RoutedEventArgs e) 
{
    Thread.Sleep(1000);
    string text = File.ReadAllText(@"c:\temp\log.txt");
    myTextBox.Text = text;
}

The last example and this one executes the same. Both doesn't block the UI while it does it jobs.

最后一个例子和这个例子执行相同。两者在工作时都不会阻塞 UI。

In conclusion, think of SynchronizationContext as a thread. It's not a thread, it defines a thread (Note that not all thread has a SyncContext). Whenever we call the Post or Send method on it to update a UI, it's just like updating the UI normally from the main UI thread. If, for some reasons, you need to update the UI from a different thread, make sure that thread has the main UI thread's SyncContext and just call the Send or Post method on it with the method that you want to execute and you're all set.

总之,将 SynchronizationContext 视为一个线程。它不是线程,它定义了一个线程(请注意,并非所有线程都有 SyncContext)。每当我们调用它的 Post 或 Send 方法来更新 UI 时,就像从主 UI 线程正常更新 UI 一样。如果出于某些原因,您需要从不同的线程更新 UI,请确保该线程具有主 UI 线程的 SyncContext 并使用您要执行的方法对其调用 Send 或 Post 方法,然后您就完成了放。

Hope this helps you, mate!

希望这对你有帮助,伙计!

回答by Ciro Corvino

SynchronizationContextbasically is a provider of callback delegates execution mainly responsible to assure that the delegates are run in a given execution context after a particular portion of code (incapsulated in a Task obj of .Net TPL)of a program has completed its execution.

SynchronizationContext基本上是回调委托执行的提供者,主要负责确保在程序的特定代码部分(封装在 .Net TPL 的 Task obj 中)完成执行后,委托在给定的执行上下文中运行。

From technical point of view, SC is a simple C# class that is oriented to support and provide its function specifically for Task Parallel Library objects.

从技术角度来看,SC 是一个简单的 C# 类,其面向支持和提供专门用于任务并行库对象的功能。

Every .Net application, except for console applications, has a particular implementation of this class based on the specific underlying framework, ie: WPF, WindowsForm, Asp Net, Silverlight, ecc..

除了控制台应用程序之外,每个 .Net 应用程序都有一个基于特定底层框架的此类的特定实现,即:WPF、WindowsForm、Asp Net、Silverlight 等。

The importance of this object is bound to the syncronization between results returning from asyncronous execution of code and the execution of dependent code that is waiting for results from that asyncronous work.

该对象的重要性与代码异步执行返回的结果与等待异步工作结果的依赖代码的执行之间的同步有关。

And the word "context" stands for execution context, that is the current execution context where that waiting code will be executed, namely the syncronization beetween async code and its waiting code happens in a specific execution context, thus this object it is named SynchronizationContext: it represents the execution context that will look after of syncronization of async code and waiting code execution.

而“上下文”这个词代表执行上下文,即等待代码将被执行的当前执行上下文,即异步代码与其等待代码之间的同步发生在特定的执行上下文中,因此该对象被命名为 SynchronizationContext:它表示将负责异步代码同步和等待代码执行的执行上下文