为啥控制台应用程序中没有捕获默认的 SynchronizationContext?
Posted
技术标签:
【中文标题】为啥控制台应用程序中没有捕获默认的 SynchronizationContext?【英文标题】:Why the default SynchronizationContext is not captured in a Console App?为什么控制台应用程序中没有捕获默认的 SynchronizationContext? 【发布时间】:2019-03-12 05:10:04 【问题描述】:我正在尝试更多地了解SynchronizationContext
,所以我制作了这个简单的控制台应用程序:
private static void Main()
var sc = new SynchronizationContext();
SynchronizationContext.SetSynchronizationContext(sc);
DoSomething().Wait();
private static async Task DoSomething()
Console.WriteLine(SynchronizationContext.Current != null); // true
await Task.Delay(3000);
Console.WriteLine(SynchronizationContext.Current != null); // false! why ?
如果我理解正确,await
运算符会捕获当前的SynchronizationContext
,然后将其余的异步方法发布给它。
但是,在我的应用程序中,SynchronizationContext.Current
在await
之后为空。这是为什么呢?
编辑:
即使我使用自己的SynchronizationContext
,它也不会被捕获,尽管它的Post
函数被调用。这是我的 SC:
public class MySC : SynchronizationContext
public override void Post(SendOrPostCallback d, object state)
base.Post(d, state);
Console.WriteLine("Posted");
这就是我使用它的方式:
var sc = new MySC();
SynchronizationContext.SetSynchronizationContext(sc);
谢谢!
【问题讨论】:
Link:首先尝试获取当前同步上下文。如果当前上下文实际上只是基本 SynchronizationContext 类型,其目的是等同于根本没有当前 SynchronizationContext,则忽略它。这通过避免不必要的帖子和工作项排队来帮助提高性能,但更重要的是,它确保如果代码碰巧将默认上下文发布为当前,它不会阻止使用当前任务调度程序(如果有的话)。 P.S.您的Post
实现不正确。它必须确保在适当的上下文中调用委托。 base.Post
不是为你做的。
@PetSerAl "如果当前上下文真的只是基本 SynchronizationContext 类型..." 但MySC
不是基本 SC 类型;表示syncCtx.GetType() != typeof(SynchronizationContext)
为真。
@PetSerAl 是的,我的Post
不正确.. 这只是用于学习目的的假实现:)
正如你所说,你的Post
被调用了。这就是上下文捕获结束的地方。它只负责在捕获的上下文中调用Post
,没有别的。其他任何事情都是 Post
实施者的责任。
【参考方案1】:
“捕获”这个词太不透明了,听起来太像框架应该做的事情。误导,因为它通常在使用默认 SynchronizationContext 实现之一的程序中执行。喜欢one you get in a Winforms app。但是当你自己编写框架时,框架就不再有用了,你需要去做。
异步/等待管道为上下文提供了在特定线程上运行延续(等待之后的代码)的机会。这听起来像是一件微不足道的事情,因为您以前经常这样做,但实际上非常困难。不能任意中断该线程正在执行的代码,这会导致可怕的重入错误。线程有帮助,它需要解决标准producer-consumer problem。采用线程安全队列和清空该队列的循环,处理调用请求。重写后的 Post 和 Send 方法的工作是将请求添加到队列中,线程的工作是使用循环清空队列并执行请求。
Winforms、WPF 或 UWP 应用程序的主线程都有这样的循环,它由 Application.Run() 执行。使用知道如何向其提供调用请求的相应 SynchronizationContext,分别是 WindowsFormsSynchronizationContext、DispatcherSynchronizationContext 和 WinRTSynchronizationContext。 ASP.NET 也可以,使用 AspNetSynchronizationContext。所有这些都由框架提供,并由类库管道自动安装。他们在构造函数中捕获同步上下文,并在其 Post 和 Send 方法中使用 Begin/Invoke。
当您编写自己的 SynchronizationContext 时,您现在必须注意这些细节。在您的 sn-p 中,您没有覆盖 Post 和 Send,而是继承了基本方法。他们一无所知,只能在任意线程池线程上执行请求。所以 SynchronizationContext.Current 现在在该线程上为空,线程池线程不知道请求来自哪里。
创建自己的并不是那么困难,ConcurrentQueue 和委托有助于大量减少代码。很多程序员都这样做了,this library 经常被引用。但是要付出沉重的代价,调度程序循环从根本上改变了控制台模式应用程序的行为方式。它阻塞线程直到循环结束。就像 Application.Run() 一样。
您需要一种非常不同的编程风格,即您在 GUI 应用程序中所熟悉的那种。代码不能花太长时间,因为它会阻塞调度程序循环,从而阻止调用请求被调度。在一个 GUI 应用程序中,由于 UI 变得无响应而非常明显,在您的示例代码中,您会注意到您的方法完成速度很慢,因为继续无法运行一段时间。你需要一个工作线程来分拆慢代码,没有免费的午餐。
值得注意的是为什么这些东西存在。 GUI 应用程序有一个严重的问题,它们的类库从来都不是线程安全的,也不能通过使用lock
来保证安全。正确使用它们的唯一方法是从同一个线程进行所有调用。 InvalidOperationException 如果您不这样做。他们的调度程序循环帮助你做到这一点,为 Begin/Invoke 和 async/await 提供动力。控制台没有这个问题,任何线程都可以向控制台写入内容,并且锁定可以帮助防止它们的输出混合。因此控制台应用程序不需要自定义 SynchronizationContext。 YMMV。
【讨论】:
【参考方案2】:默认情况下,控制台应用程序和 Windows 服务中的所有线程只有默认的SynchronizationContext
。
请参考 MSDN 文章Parallel Computing - It's All About the SynchronizationContext。这有关于SynchronizationContext
s 在各种类型的应用程序中的详细信息。
【讨论】:
【参考方案3】:详细说明已经指出的内容。
您在第一个代码 sn-p 中使用的 SynchronizationContext
类是 default 实现,它不做任何事情。
在第二个代码 sn-p 中,您创建自己的 MySC
上下文。但是你错过了真正使它起作用的位:
public override void Post(SendOrPostCallback d, object state)
base.Post(state2 =>
// here we make the continuation run on the original context
SetSynchronizationContext(this);
d(state2);
, state);
Console.WriteLine("Posted");
【讨论】:
但是等待之后的代码还在另一个线程中继续,为什么会这样?【参考方案4】:实现您自己的SynchronizationContext
是可行的,但并非易事。使用现有实现要容易得多,例如 Nito.AsyncEx.Context 包中的 AsyncContext
类。你可以这样使用它:
using System;
using System.Threading;
using System.Threading.Tasks;
using Nito.AsyncEx;
public static class Program
static void Main()
AsyncContext.Run(async () =>
await DoSomethingAsync();
);
static async Task DoSomethingAsync()
Console.WriteLine(SynchronizationContext.Current != null); // True
await Task.Delay(3000);
Console.WriteLine(SynchronizationContext.Current != null); // True
Try it on Fiddle.
AsyncContext.Run
是一种阻塞方法。当提供的异步委托 Func<Task> action
完成时,它将完成。所有异步延续都将在控制台应用程序的主线程上运行,前提是没有 Task.Run
或 ConfigureAwait(false)
会强制您的代码退出上下文。
在控制台应用程序中使用单线程SynchronizationContext
的后果是:
-
您不必再担心线程安全,因为您的所有代码都将集中到一个线程中。
您的代码易受deadlocks 的影响。代码中的任何
.Wait()
、.Result
、.GetAwaiter().GetResult()
等都很可能导致您的应用程序冻结,在这种情况下,您必须从 Windows 任务管理器手动终止该进程。
【讨论】:
以上是关于为啥控制台应用程序中没有捕获默认的 SynchronizationContext?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Firebug 不为未定义的属性显示“未捕获的类型错误”?
为啥 xcode 编辑方案中的 GPU 帧捕获中没有 GLES 选项