VR全景图片 csv button background axios Ractivejs 两个正态分布相乘 android富文本框架 eclipse闪退 docker导入镜像 数据库教程 python数据 python支持中文 python循环10次 python自定义异常 python导入文件 python学习方法 java教材 java学习文档 java的方法 java中continue java中的集合 java接口的修饰符 vbscript程序员参考手册 java分布式开发 vnc客户端 脚本大全 服务器系统安装 netreflector 视频加字幕软件 jlabel 掘地鼠炖肉 a1474 色阶快捷键 wps苹果mac版 数据库同步解决方案 curdate redis密码设置 ps测量工具 ppt去掉背景音乐
当前位置: 首页 > 学习教程  > python

同步上下文(SynchronizationContext)

2021/2/6 22:22:38 文章标签: 测试文章如有侵权请发送至邮箱809451989@qq.com投诉后文章立即删除

文章目录前言一、简述1、概念2、同步上下文的发展1、原始多线程的环境2、ISynchronizeInvoke 的诞生3、SynchronizationContext 的诞生二、同步上下文的实现1、WinForm 的同步上下文2、WPF的同步上下文3、默认同步上下文4、ASPNET同步上下文5、使用上下文捕获和执行的问题6、有…

文章目录

  • 前言
  • 一、简述
    • 1、概念
    • 2、同步上下文的发展
      • 1、原始多线程的环境
      • 2、ISynchronizeInvoke 的诞生
      • 3、SynchronizationContext 的诞生
  • 二、同步上下文的实现
    • 1、WinForm 的同步上下文
    • 2、WPF的同步上下文
    • 3、默认同步上下文
    • 4、ASPNET同步上下文
    • 5、使用上下文捕获和执行的问题
    • 6、有关同步上下文实现的说明
  • 三、AsyncOperationManager和AsyncOperation
  • 四、同步上下文的库支持示例
    • 1、WCF
    • 2、WF
    • 3、任务并行库(TPL)
    • 4、Reactive Extensions (Rx)
    • 5、异步编程 Async
  • 总结


前言

由于之前对同步上下文并不了解,经过各种资料搜索,梳理排版此篇,算是对该知识点一个梳理。本片文章大部分参考MSDN杂志,并加入了一部分自己的理解,理解不到位的地方还请指出。

MSDN的原文链接


一、简述

1、概念

同步上下文是一种可以将工作单元排队到上下文(主要是不同的线程)的方法。
它的作用通俗来讲就是实现线程之间通讯的。

同步上下文应用于很多场景,比如在WinForms和WPF中,只有一个UI线程可以更新UI元素(文本框,复选框等)。如果尝试从另一个非UI线程更改文本框的内容,则不会发生更改,也可能抛出异常(取决于UI框架)。因此,在这样的应用程序中,非UI线程需要将对UI元素的所有更改安排到UI线程。这就是同步上下文提供的内容。它允许将一个工作单元(执行某些方法)发布到不同的上下文 - 在这种情况下是UI线程。

无论使用哪种平台(ASP.NET 、WinForm 、WPF 等),所有.NET程序都包含同步上下文的概念。Microsoft .NET Framework提供了同步上下文的SynchronizationContext类。根据平台框架不同,又单独提供了WindowsFormsSynchronizationContext(WinForm)类、DispatcherSynchronizationContext(WPF)类等同步上下文的模型但都是继承自SynchronizationContext类。

2、同步上下文的发展

1、原始多线程的环境

  • 多线程程序存在早于 .NET Framework 出现
  • 多线程程序通常需要需要一个线程将工作单元传递给另一线程
  • Windows程序以消息循环为中心,因此许多程序员使用此内置队列来传递工作单元
  • 每个要以这种方式使用 Windows消息队列的多线程程序都必须自定义 Windows 消息以及处理约定

多线程会使事情复杂化的一种非常常见的情况是更新用户界面,由于Winforms线程不安全,因此通常必须从UI线程完成。通过将一些代码从调用线程编组到主UI线程,从而使代码本身在UI线程上执行,从而完全避免同步问题。由于所有与UI相关的工作都在UI线程上完成,因此没有同步问题。

2、ISynchronizeInvoke 的诞生

当 .NET Framework 首次发布时,这一通用模式已经标准化。那时 .NET 唯一支持的 GUI 应用程序类型是 WinFrom。不过,框架设计人员期待其他模型,他们开发出了一种通用的解决方案,ISynchronizeInvoke 诞生了。
ISynchronizeInvoke 的原理是让“源”线程可以将委托排队到“目标”线程队列中,可以选择等待该委托完成。ISynchronizeInvoke还提供了一个属性来确定当前代码是否已在目标线程上运行;在这种情况下,不需要委托排队。
Windows窗体提供了ISynchronizeInvoke的唯一实现,并且开发了一种用于设计异步组件的模式。

3、SynchronizationContext 的诞生

.NET Framework 2.0版包含许多重大更改。主要改进之一是将异步页面引入ASP.NET体系结构。在.NET Framework 2.0之前,每个ASP.NET请求都需要一个线程,直到请求完成为止。这是对线程的低效率使用,因为创建Web页面通常取决于数据库查询和对Web服务的调用,并且处理该请求的线程将不得不等待,直到每个操作都完成为止。对于异步页面,处理请求的线程可以开始每个操作,然后返回到ASP.NET线程池。操作完成后,ASP.NET线程池中的另一个线程将完成请求。

但是,ISynchronizeInvoke不太适合ASP.NET异步页面体系结构。使用ISynchronizeInvoke模式开发的异步组件在ASP.NET页面中无法正常工作,因为ASP.NET异步页面未与单个线程关联。异步页面无需排队工作到原始线程,仅需要维护未完成操作的计数来确定何时可以完成页面请求。经过深思熟虑和精心设计,ISynchronizeInvoke被SynchronizationContext取代。

ISynchronizeInvoke满足两个需求:
• 确定是否需要同步
• 将工作单元从一个线程排队到另一个线程

设计 SynchronizationContext 是为了替代 ISynchronizeInvoke ,但完成设计后,它就不仅仅是一个替代品了。SynchronizationContext与ISynchronizeInvoke 不同,具有以下新特点:

  1. SynchronizationContext提供了一种方式,可以使工作单元列队并列入上下文。
    • 注意,工作单元是列入上下文,而不是某个特定线程
    • 这一区别非常重要,因为很多 SynchronizationContext 实现都不是基于单个特定线程的
    • SynchronizationContext不包含用来确定是否必须同步的机制,因为这是不可能的。
    • WPF 中的Dispatcher.Invoke是将委托列入上下文,不等委托执行直接返回
    • WinForm 中的txtUName.Invoke会启动一个process,等到委托执行完毕后返回
  2. 每个线程都有当前上下文。
    • 线程上下文不一定唯一;
    其上下文实例可以与多个其他线程共享
    • 线程可以更改其当前上下文,但这样的情况非常少见。
  3. 保留了未完成异步操作的计数。
    • 可以用于 ASP.NET 异步页面和需要此类计数的任何其他主机。
    • 大多数情况下,捕获到当前 SynchronizationContext 时,计数递增;
    • 捕获到的 SynchronizationContext 用于将完成通知列队到上下文中时,计数递减 void OperationCompleted()。

还有其他特点,但是对于大多数程序员而言,它们的重要性并不高。

SynchronizationContext API:

// SynchronizationContext API的重要方面
class SynchronizationContext
{
 
  // 将工作分配到上下文中
  void Post(..); // (asynchronously 异步)
 
  void Send(..); // (synchronously 同步)
 
  // 跟踪异步操作的数量。
  void OperationStarted();
 
  void OperationCompleted();
 
  // 每个线程都有一个Current Context。
  // 如果“Current”为null,则按照惯例,
  // 最开始的当前上下文为 new SynchronizationContext()。
  static SynchronizationContext Current { get; }
 
  //设置当前同步上下文
  static void SetSynchronizationContext(SynchronizationContext);
}

二、同步上下文的实现

不同的框架和主机可以自行定义上下文。通过了解这些不同的实现及其限制,可以清楚了解 SynchronizationContext 概念可以和不可以实现的功能。

1、WinForm 的同步上下文

WindowsFormsSynchronizationContext
命名空间:System.Windows.Forms.dll:System.Windows.Forms

实现:

  • Windows Forms应用程序将创建WindowsFormsSynchronizationContext并将其安装为创建UI控件的任何线程的当前上下文。
  • 此SynchronizationContext在UI控件上使用ISynchronizeInvoke方法,该控件将委托传递到基础Win32消息循环。
  • WindowsFormsSynchronizationContext的上下文是单个UI线程。

排队到WindowsFormsSynchronizationContext的所有委托一次执行一次;它们由特定的UI线程按排队顺序执行。当前实现为每个UI线程创建一个WindowsFormsSynchronizationContext。

2、WPF的同步上下文

DispatcherSynchronizationContext
命名空间:WindowsBase.dll:System.Windows.Threading

实现:

  • Dispatcher的作用是用于管理线程工作项队列,类似于Win32中的消息队列,Dispatcher的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。
  • WPF和Silverlight应用程序使用DispatcherSynchronizationContext,该代理将对UI线程的Dispatcher的委托以“Normal”优先级排队。
  • 当线程通过调用Dispatcher.Run开始循环调度器 ,将这个初始化完成的 同步上下文 安装到当前上下文。
  • DispatcherSynchronizationContext的上下文是单个UI线程。

排队到DispatcherSynchronizationContext的所有委托均由特定的UI线程一次按其排队的顺序执行。当前实现为每个顶级窗口创建一个DispatcherSynchronizationContext,即使它们都共享相同的基础Dispatcher。

3、默认同步上下文

(默认)SynchronizationContext
命名空间:mscorlib.dll:System.Threading
调度线程池线程的同步上下文。

实现:

  • 默认SynchronizationContext是默认构造的SynchronizationContext对象。根据惯例,如果线程的当前SynchronizationContext为null,则它隐式具有默认的SynchronizationContext。
  • 默认的同步上下文将异步委托排队到ThreadPool,但直接在调用线程上执行其同步委托。
  • 因此,默认同步上下文涵盖所有ThreadPool线程以及任何调用Send的线程。默认SynchronizationContext“借用”线程调用Send,将它们带入上下文,直到委托完成。从这个意义上讲,默认上下文可以包括进程中的任何线程。

除非代码由ASP.NET托管,否则默认的SynchronizationContext将应用于ThreadPool线程。除非子线程设置自己的SynchronizationContext,否则默认的SynchronizationContext也会隐式应用于显式子线程(Thread类的实例)。因此,UI应用程序通常具有两个同步上下文:覆盖UI线程的UI SynchronizationContext覆盖ThreadPool线程的默认SynchronizationContext

4、ASPNET同步上下文

AspNetSynchronizationContext
命名空间:System.Web.dll:System.Web [internal class]

实现:

  • ASP.NET 同步上下文在执行页面代码时安装在线程池线程上。当委托排队到捕获的AspNetSynchronizationContext时,它将还原原始页面的标识和区域性,然后直接执行委托。即使通过调用Post将其“异步”排队,也可以直接调用该委托。
  • 从概念上讲,AspNetSynchronizationContext的上下文很复杂。在异步页的生存期内,上下文仅从ASP.NET线程池中的一个线程开始。启动异步请求后,上下文不包含任何线程。随着异步请求的完成,执行其完成例程的线程池线程将进入上下文。这些线程可能是发起请求的线程,但更有可能是操作完成时空闲的任何线程。

如果对同一应用程序一次完成多个操作,AspNetSynchronizationContext将确保它们一次执行一个。它们可以在任何线程上执行,但是该线程将具有原始页面的标识和区域性。

一个常见的示例是在异步网页中使用的WebClient。DownloadDataAsync将捕获当前的SynchronizationContext,稍后将在该上下文中执行其DownloadDataCompleted事件。当页面开始执行时,ASP.NET将分配其线程之一以执行该页面中的代码。该页面可以调用DownloadDataAsync然后返回;ASP.NET保留未完成的异步操作的计数,因此它了解页面不完整。WebClient对象下载了请求的数据后,它将在线程池线程上收到通知。该线程将在捕获的上下文中引发DownloadDataCompleted。上下文将保留在同一线程上,但将确保事件处理程序以正确的身份和区域性运行

5、使用上下文捕获和执行的问题

许多基于事件的异步组件无法使用默认的SynchronizationContext正常工作。如,在一个UI应用程序,其中一个BackgroundWorker启动了另一个BackgroundWorker。每个BackgroundWorker捕获并使用调用RunWorkerAsync的线程的SynchronizationContext,然后在该上下文中执行其RunWorkerCompleted事件。

在单个BackgroundWorker的情况下,这通常是基于UI的SynchronizationContext,因此RunWorkerCompleted在RunWorkerAsync捕获的UI上下文中执行。

UI上下文中的单个BackgroundWorker
但是,如果BackgroundWorker从其DoWork处理程序启动另一个BackgroundWorker,则嵌套的BackgroundWorker不会捕获UI SynchronizationContext。DoWork由具有默认SynchronizationContext的ThreadPool线程执行。在这种情况下,嵌套的RunWorkerAsync将捕获默认的SynchronizationContext,因此它将在ThreadPool线程而不是UI线程上执行其RunWorkerCompleted。

在这里插入图片描述
默认情况下,控制台应用程序和Windows Services中的所有线程仅具有默认的SynchronizationContext。这会导致某些基于事件的异步组件失败。
两种解决方案:

  • 创建一个显式子线程并在该线程上安装SynchronizationContext,然后可以为这些组件提供上下文。
  • 实现SynchronizationContext,可参考Nito.Async库(nitoasync.codeplex.com或https://github.com/StephenClearyArchive/Nito.Asynchronous)的ActionThread类可以用作通用的SynchronizationContext实现。

6、有关同步上下文实现的说明

SynchronizationContext提供了一种编写可以在许多不同框架中工作的组件的方法。BackgroundWorker和WebClient是Windows Forms,WPF,Silverlight,控制台和ASP.NET应用程序中同样常见的两个示例。

SynchronizationContext实现是无法比较的。
这意味着没有等效的ISynchronizeInvoke.InvokeRequired。代码更简洁,更容易验证它是否始终在已知上下文中执行,而不是尝试处理多个上下文。

并非所有的SynchronizationContext实现都保证委托执行或委托同步的顺序。
基于UI的SynchronizationContext实现确实满足这些条件,但是ASP.NET SynchronizationContext仅提供同步。默认的SynchronizationContext不保证执行顺序或同步顺序。
SynchronizationContext实例与线程之间没有1:1的对应关系。
WindowsFormsSynchronizationContext确实具有到线程的1:1映射(只要不调用SynchronizationContext.CreateCopy即可),但这在任何其他实现中均不成立。通常,最好不要假定任何上下文实例都可以在任何特定线程上运行。

SynchronizationContext.Post方法不一定是异步的。
大多数实现都异步实现它,但是AspNetSynchronizationContext是一个明显的例外。这可能会导致意外的重新进入问题。

SynchronizationContext实现摘要:

使用特定线程 执行委托独占 (一次执行一个委托)有序 (委托按队列顺序执行)Send 可以直接调用委托Post 可以直接调用委托
Winform如果从UI线程调用从不
WPF/Silverlight如果从UI线程调用从不
Default总是从不
ASP.NET总是总是

三、AsyncOperationManager和AsyncOperation

NET Framework中的AsyncOperationManager和AsyncOperation类是ynchronizationContext抽象的轻量级封装。本人经常使用Unity,很多时候使用AsyncOperationManager和AsyncOperation实现异步操作。

实现:

  • AsyncOperationManager首次创建AsyncOperation时会捕获当前的SynchronizationContext,如果当前的SynchronizationContext为null,则将其替换为默认的SynchronizationContext。
  • AsyncOperation将委托异步发布到捕获的SynchronizationContext

大多数基于事件的异步组件在其实现中都使用AsyncOperationManager和AsyncOperation。这些方法对于具有定义的完成点的异步操作非常有效,也就是说,异步操作在一个点开始,在另一个点结束。其他异步通知可能没有定义的完成点。这些可能是一种订阅,从一个点开始,然后无限期地继续。对于这些类型的操作,可以直接捕获并使用SynchronizationContext。

新组件不应使用基于事件的异步模式,应采用基于任务的异步模式。组件返回TaskTask<TResult>对象,而不是通过SynchronizationContext引发事件。基于任务的API是.NET中异步编程的未来,不过就目前来说,TAP已经是异步普遍采用的模式了。

四、同步上下文的库支持示例

BackgroundWorker和WebClient之类的简单组件本身就隐式可移植,从而隐藏了同步上下文的捕获和用法。许多库对SynchronizationContext都有更明显的用途。通过使用SynchronizationContext公开API,库不仅获得框架独立性,而且还为高级最终用户提供了可扩展性。

目前的SynchronizationContext被认为是ExecutionContext的一部分,此外ExecutionContext还包括安全上下文调用上下文同步上下文。捕获线程的ExecutionContext的任何系统都将捕获当前的SynchronizationContext。恢复ExecutionContext时,通常也恢复SynchronizationContext。

1、WCF

Windows Communication Foundation(WCF):UseSynchronizationContext

WCF具有两个用于配置服务器和客户端行为的属性:ServiceBehaviorAttribute和CallbackBehaviorAttribute。这两个属性都有一个Boolean 属性:UseSynchronizationContext。此属性的默认值为true,这意味着在创建通信通道时将捕获当前的SynchronizationContext,并且使用此捕获的SynchronizationContext来对协定方法进行排队。

  • 服务器使用默认的SynchronizationContext
  • 客户端回调使用适当的UI SynchronizationContext

但是当需要重新输入时,这可能会引起问题,例如客户端调用调用客户端回调的服务器方法。在这种情况下,可以通过将UseSynchronizationContext设置为false来禁用WCF对SynchronizationContext的自动使用。
参考文档:msdn.microsoft.com/magazine/cc163321

2、WF

Windows Workflow Foundation(WF):WorkflowInstance.SynchronizationContext

WF主机最初使用WorkflowSchedulerService和派生类型来控制如何在线程上计划工作流活动。.NET Framework 4升级的一部分包括WorkflowInstance类及其派生的WorkflowApplication类的SynchronizationContext属性。
如果托管进程创建了自己的WorkflowInstance,则可以直接设置SynchronizationContext。
WorkflowInvoker.InvokeAsync也使用SynchronizationContext,它捕获当前的SynchronizationContext并将其传递给其内部WorkflowApplication。然后,此SynchronizationContext用于发布工作流完成事件以及工作流活动。

3、任务并行库(TPL)

Task Parallel Library (TPL): TaskScheduler.FromCurrentSynchronizationContext 和 CancellationToken.Register

TPL 使用任务(Task)对象作为其工作单元并通过任务调度( TaskScheduler )执行。

  • 默认TaskScheduler的作用类似于默认同步上下文,将任务排队到ThreadPool。
  • TPL队列提供了另一个TaskScheduler,它将任务排队到同步上下文。
  • UI更新的进度报告可以使用嵌套任务来完成。
private void button1_Click(object sender, EventArgs e)
{
  // 捕捉当前上下文的任务调度
  TaskScheduler taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  // 开始一个新任务(使用默认任务调度)
  // 所以它将运行在一个线程池线程
  Task.Factory.StartNew(() =>
  {
      //运行一个线程池线程
      
    ; 
      //报告UI进度
    Task reportProgressTask = Task.Factory.StartNew(() =>
      {
        // We are running on the UI thread here.
        ; // Update the UI with our progress.
      },
      CancellationToken.None,
      TaskCreationOptions.None,
      taskScheduler);
      
    reportProgressTask.Wait();
  
    ; 
  });
}

CancellationToken类用于.NET Framework 4中的任何类型的取消操作。
为了与现有的取消形式集成,此类允许注册委托以在请求取消时调用。当委托被注册时,可以传递SynchronizationContext。当请求取消时,CancellationToken将委托排队到SynchronizationContext中,而不是直接执行它。

4、Reactive Extensions (Rx)

Microsoft Reactive Extensions(Rx):ObserveOn, SubscribeOn和SynchronizationContextScheduler
Rx是一个库,它将事件视为数据流。

  • ObserveOn(context) 运算符通过一个 同步上下文 将事件列队
  • SubscribeOn(context) 运算符通过一个 同步上下文 将对这些事件的订阅 列队

ObserveOn(context) 通常用于使用传入事件更新 UI,SubscribeOn 用于从 UI 对象使用事件
Rx还具有其自己的工作单元排队方式:IScheduler接口。
Rx包括SynchronizationContextScheduler。

  • 是一个将 Task 列入指定 同步上下文 的 IScheduler 实现
  • 构造方法: SynchronizationContextScheduler(SynchronizationContext context)

5、异步编程 Async

await 、 ConfigureAwait 、 SwitchTo 和 Progress<T>

默认情况下, 当前同步上下文在一个 await 关键字处被捕获,捕获的同步上下文用于在运行到await后时恢复。也就是await关键字后面的执行代码会被列入到该同步上下文中执行。
若捕获当前的SynchronizationContext为NULL,则捕获当前 TaskScheduler。

private async void button1_Click(object sender, EventArgs e)
{
  // 当前 SynchronizationContext 被 await 在暗中捕获
  var data = await webClient.DownloadStringTaskAsync(uri);
 
  // 此时,已捕获的SynchronizationContext用于恢复执行,
  // 因此我们可以自由更新UI对象。
}

ConfigureAwait提供了一种避免默认上下文捕获行为的方法。参数传递false会阻止使用SynchronizationContext在等待之后恢复执行。
在同步上下文实例上还有一个扩展方法,称为SwitchTo。这允许任何异步方法通过调用SwitchTo并等待结果来更改为不同的同步上下文。

异步CTP引入了一种报告异步操作进度的通用模式:IProgress 接口及其实现EventProgress 。此类在构造时捕获当前的SynchronizationContext,并在该上下文中引发其ProgressChanged事件

IProgress 接口及其实现 Progress
• 该类在构造时捕获当前同步上下文
• 并在中引发其ProgressChanged 事件
• 所以实例化时,需要在UI同步上下文上执行

返回 void 的 async 方法
• 在异步操作开始时递增计数
• 在异步操作结束后递减计数
这一行为使返回 void 的 async 方法类似于顶级异步操作。

总结

本文仅仅简单介绍了同步上下文,了解SynchronizationContext对任何程序员都有帮助。现有的跨框架组件使用它来同步其事件。


本文链接: http://www.dtmao.cc/news_show_2000043.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?