SynchronizationContext有什么作用?

clo*_*Fan 116 .net c# multithreading

在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);
});
Run Code Online (Sandbox Code Playgroud)

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

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

sta*_*ica 151

SynchronizationContext有什么作用?

简单地说,SynchronizationContext代表一个位置"where"代码可能被执行.然后将在该位置调用传递给它SendPost方法的代理.(Post是非阻塞/异步版本Send.)

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

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

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

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

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

这就是为什么你必须以某种方式从工作线程"切换回"到myTextBox特定赋值之前的"UI线程"(创建的地方).这样做如下:

  1. 当您仍在UI线程上时,在SynchronizationContext那里捕获Windows窗体,并在变量(originalContext)中存储对它的引用以供以后使用.你必须SynchronizationContext.Current在这一点上查询; 如果您在传递给代码的内部查询它ThreadPool.QueueUserWorkItem,您可能会获得与线程池的工作线程关联的任何同步上下文.存储对Windows窗体上下文的引用后,您可以随时随地使用它将代码"发送"到UI线程.

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


最后的评论和提示:

  • 什么同步上下文不会为您做什么告诉您哪些代码必须在特定位置/上下文中运行,哪些代码可以正常执行,而不将其传递给SynchronizationContext.为了确定这一点,您必须知道您正在编程的框架的规则和要求 - 在这种情况下是Windows窗体.

    所以请记住Windows窗体的这个简单规则:不要从创建它们的线程以外的线程访问控件或表单.如果必须这样做,请使用上述SynchronizationContext机制,或Control.BeginInvoke(这是Windows Forms特定的完全相同的方式).

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

  • @ user34660:不,这不正确.你*可以*有几个创建Windows窗体控件的线程.但是每个控件都与创建它的一个线程相关联,并且只能由该一个线程访问.来自不同UI线程的控件在它们彼此交互的方式上也非常有限:一个不能是另一个的父/子,它们之间的数据绑定是不可能的等等.最后,创建控件的每个线程都需要它自己的消息循环(由`Application.Run`,IIRC启动).这是一个相当高级的主题,而不是随便做的事情. (3认同)
  • 所有关于“ Windows”和“ Windows Windows”的话题都让我感到头晕。我有没有提到这些“窗户”?我不这么认为 (2认同)

nos*_*tio 21

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

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

以下是对同步上下文的非常好的解释: 它是All About SynchronContext.


Han*_*ant 15

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

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

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

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

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

这本书试图教你线程,所以使用这个有缺陷的例子是可以的.在现实生活中,在你的少数情况下可能会考虑使用SynchronizationContext.Current,你还是把它留给C#的异步/等待关键字或TaskScheduler.FromCurrentSynchronizationContext()来为你做它.但请注意,出于完全相同的原因,当你在错误的线程上使用它们时,它们仍然行为不正常.这里有一个非常常见的问题,额外的抽象级别很有用,但却很难弄清楚它们无法正常工作的原因.希望这本书还告诉你何时不使用它:)

  • 你的英语难以解码.从UI线程调用原始代码段时,原始代码段才能正常工作.这是一个非常常见的情况.只有这样它才会回发到UI线程.如果从工作线程调用它,那么Post()委托目标将在线程池线程上运行.KABOOM.这是你想要自己尝试的东西.启动一个线程,让线程调用此代码.如果代码与NullReferenceException崩溃,你就做对了. (4认同)

Cir*_*ino 7

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

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

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

该对象的重要性与从异步执行代码返回的结果和等待该异步工作结果的相关代码的执行之间的同步密切相关。

“上下文”一词代表执行上下文。也就是说,将执行等待代码的当前执行上下文,即异步代码与其等待代码之间的同步发生在特定的执行上下文中。因此这个对象被命名为SynchronizationContext。

它表示将负责异步代码同步和等待代码执行的执行上下文


Eri*_*sch 5

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

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

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

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

  • _“是的,您显示的代码基本上没有用。为什么要创建后台线程,而只需要立即返回到主UI线程?这只是一个例子。” _-从文件读取可能是一个漫长的任务如果文件很大,则可能会阻塞UI线程并使其无响应 (4认同)

Big*_*yes 5

至源头

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

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

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

  • 同步上下文不一定与特定线程相关联。多个线程可以处理对单个同步上下文的请求,并且单个线程可以处理对多个同步上下文的请求。 (4认同)
  • @DavidKlempfner 问题不在于答案中的引用,而在于作者对此的评论。你引用的那一点不是问题。作者的声明是,如果您从线程获取同步上下文,则意味着它将在该线程上执行已发布的委托。答案引用的部分只是说线程可能有上下文,而没有评论发布到它时实际发生的情况。 (2认同)