有关C#中的同步上下文 (转自Butterapple)
2022/2/20 22:28:42
本文主要是介绍有关C#中的同步上下文 (转自Butterapple),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录- C# 同步上下文及死锁
- 1,同步上下文的概念及其历史
- 2,如何理解同步上下文
- 3,说下 ConfigureAwait
- 4,说下 死锁
- 5,说下 async void
C# 同步上下文及死锁
1,同步上下文的概念及其历史
在 .Net 之前,多线程的应用程序就已经存在了,这些程序经常需要把比如当前线程的 工作状态或者上下文传递到另一个线程中,在 windows中,程序都是以一个总的消息循环中心来分发所有信息的。所以最开始的时候,大家都会通过定义自己的windows消息和处理他的约定来进行处理。
我们都知道的是 Windows 上的程序是以 消息循环为中心的,这个如何理解呢?
每一个 window 窗体都有一个与之关联的 Window Procedure,这个是一个用来处理所有消息发送或发送到类的给所有消息的函数。窗体的所有UI显示和显示都取决于 Window Procedure 对这些消息的响应。
当 .Net Framework 首次发布时,当时唯一的一个 GUI应用就是 Windows Form。他们的框架设计者开发了一个通用的解决方案 -> ISynchronizeInvoke 诞生啦。
当时框架设计者设计 ISynchroizeInvoke 背后的想法是这样的:源线程 可以对 目标线程的委托 进行排队,可以选择等待该委托完成。并且 ISynchronizeInvoke 还提供了一个属性来确定当前代码是否已经在 目标线程上运行,在这种情况下,就没有必要排队等待委托了。
然而当 .Net Framework 2.0 发布后,发现这种模式又不适用了,主要是因为创建Web页面通常依赖于数据库查询和对Web服务的调用,处理该请求的线程必须等待,直到这些操作完成。
后面,框架设计者设计出了 SynchronizationContext,这就是我们呢到现如今,.Net Framework4.8 .NET 5 还在使用的同步上下文 模型
2,如何理解同步上下文
Provides the basic functionality for propagating a synchronization context in various synchronization models. 提供在各种同步模型中传播 同步上下文的功能。
1,它提供了一种将工作单元排队到上下文的方法。请注意,此工作单元是排队到一个上下文,而不是特定的线程。这个区别很重要,因为许多SynchronizationContext的实现不是基于单个的、特定的线程。
2,每个线程都有一个当前上下文。一个线程的上下文不一定是唯一的;它的上下文实例可以与其他线程共享。一个线程可以改变它的当前上下文,但一般很少。
3,它保存了未完成异步操作的计数。这使得使用ASP.NET异步页面和任何其他需要这种计数的主机。在大多数情况下,当捕获当前的SynchronizationContext时,该计数将增加,而当捕获的SynchronizationContext用于将完成通知排队发送到该上下文时,该计数将减少。
WindowsFormsSynchronizationContext (System.Windows.Forms.dll)
1,Use ISynchronizeInvoke on UI Control,用来将委托传递 给 win32 message loop
2,每一个 UI 线程 都会创建一个 WindowsFormsSynchronizationContext
3,WindowsFormsSynchronizationContext 的上下文是一个 单一的UI 线程
DispatcherSynchronizationContext(WindowsBase.dll: System.Windows.Threading)
1,以 “Normal” 优先级的委托 传递给 UI 线程。
2,所有排队到 DispatcherSynchronizationContext 的委托都是由 特定的UI线程 按照他们排队的顺序 依次执行,一次执行一个。
3,DispatcherSynchronizationContext 的上下文是一个 单一的 UI 线程
Default(ThreadPool) SynchronizationContext(mscorlib.dll: System.Threading)
1,默认的 SynchronizationContext 是一个默认构造函数的 SynchronizationContext 对象,按照惯例,如果一个线程 当前的 SynchronizationContext 是 null,那么它会 隐式的 含有一个 默认的 SynchronizationContext。
2,默认 SynchronizationContext 将它的异步委托 添加到 线程池 队列,但是 在调用的线程上执行它的同步委托。因此,它的上下文 涵盖了 所有的线程池的线程 以及 调用它的线程。上下文 “借用” 调用它的线程,然后把它们带入到上下文中 直到委托结束。从某种意义上来说,默认上下文可能包含当前进程中的任何线程。
3,默认 SynchronizationContext 是应用在 线程池中的线程的除非是被 ASP.NET 托管的代码,默认的 SynchronizationContext 也隐式应用于显式的子线程中除非子线程设置了自己的 SynchronizationContext。因此,UI 的应用一般都有两个 SynchronizationContext
, UI 的 SynchronizationContext cover UI thread, default SynchronizationContext
cover ThreadPool thread。
考虑这样一个场景:
一个窗体,上面有一个按钮,点击时从 www.baidu.com上获取一些内容。
这个Button UI 线程唯一拥有,并可以访问它,否则的话就会出现我们经常看到的一个错误
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
用 同步上下文来实现的话,就可以用这样。显式的声明回调后的函数处理。
然而,这种方式可能才是我们最经常用的。当我们使用 “await”时,做的事情呢其实和我们 主动调用 callback 进行回调处理是一样的。接下来我们深入底层来 剖析下 这个 await 具体都做了啥?
我们都知道 当我们给 一个方法 声明 async 时,这个方法会被C# 中的编译器转成 一个 StateMachine 状态机。
以下面这个简单的方法声明为例子:
private async Task Run() { await M(); } [AsyncStateMachine(typeof(<run>d__1))] [DebuggerStepThrough] private Task Run() { <run>d__1 stateMachine = new <run>d__1(); stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create(); stateMachine.<>4__this = this; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } [CompilerGenerated] private sealed class <run>d__1 : IAsyncStateMachine { public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public C <>4__this; private TaskAwaiter <>u__1; private void MoveNext() { int num = <>1__state; try { TaskAwaiter awaiter; if (num != 0) { awaiter = <>4__this.M().GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <run>d__1 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } } else { awaiter = <>u__1; <>u__1 = default(TaskAwaiter); num = (<>1__state = -1); } awaiter.GetResult(); } catch (Exception exception) { <>1__state = -2; <>t__builder.SetException(exception); return; } <>1__state = -2; <>t__builder.SetResult(); } void IAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } }
3,说下 ConfigureAwait
4,说下 死锁
直接拿 async.cs 中的状态机来讲
重点在 SetResult
疑问?为什么用 await 不会死锁,而用 Wait() 或 GetAwaiter().GetResult() 就会死锁。
下面三张可以比较形象的
5,说下 async void
如非必要,尽量不用,除了 事件处理方法声明上。
开始很容易,结束太难。因为你根本不知道什么时候它执行结束了。
很难编写单元测试代码。
这篇关于有关C#中的同步上下文 (转自Butterapple)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-03-01沐雪多租宝商城源码从.NetCore3.1升级到.Net6的步骤
- 2024-05-08首个适配Visual Studio平台的国产智能编程助手CodeGeeX正式上线!C#程序员必备效率神器!
- 2024-03-30C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】
- 2024-03-29c# datetime tryparse
- 2024-02-21list find index c#
- 2024-01-24convert toint32 c#
- 2024-01-24Advanced .Net Debugging 1:你必须知道的调试工具
- 2024-01-24.NET集成IdGenerator生成分布式全局唯一ID
- 2024-01-23用CI/CD工具Vela部署Elasticsearch + C# 如何使用
- 2024-01-23.NET开源的简单、快速、强大的前后端分离后台权限管理系统