C# 为什么控制台应用程序中未捕获默认SynchronizationContext?

C# 为什么控制台应用程序中未捕获默认SynchronizationContext?,c#,async-await,console-application,synchronizationcontext,C#,Async Await,Console Application,Synchronizationcontext,我试图了解更多有关同步上下文的信息,因此我制作了这个简单的控制台应用程序: private static void Main() { var sc = new SynchronizationContext(); SynchronizationContext.SetSynchronizationContext(sc); DoSomething().Wait(); } private static async Task DoSomething() { Console

我试图了解更多有关同步上下文的信息,因此我制作了这个简单的控制台应用程序:

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
wait
之后为空。为什么呢

编辑:

即使我使用自己的
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);

谢谢

默认情况下,控制台应用程序和Windows服务中的所有线程都只有默认的SynchronizationContext


请参考链接了解更多详细信息。这里有关于各种类型应用程序中的同步上下文的详细信息。

详细说明已经指出的内容

您在第一个代码段中使用的
SynchronizationContext
类是默认的实现,它不做任何事情

在第二个代码段中,您创建自己的
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");
}
“捕获”这个词太不透明了,听起来太像框架应该做的事情。误导,因为它通常在使用默认SynchronizationContext实现之一的程序中发生。像这样。但是,当您编写自己的框架时,框架就不再有帮助了,它就成为您的工作

async/await管道为上下文提供了在特定线程上运行延续(等待后的代码)的机会。这听起来像是一件小事,因为你以前经常这样做,但事实上相当困难。不可能任意中断此线程正在执行的代码,这将导致可怕的重入错误。线程必须提供帮助,它需要解决标准问题。接受线程安全队列和清空该队列的循环,以处理调用请求。重写的Post和Send方法的任务是向队列添加请求,线程的任务是使用循环清空队列并执行请求

Winforms、WPF或UWP应用程序的主线程有这样一个循环,它由Application.Run()执行。具有相应的SynchronizationContext,知道如何为其提供调用请求,分别是WindowsFormsSynchronizationContext、DispatcherSynchronizationContext和WinRTSynchronizationContext。ASP.NET也可以这样做,使用AspNetSynchronizationContext。所有这些都由框架提供,并由类库自动安装。他们在构造函数中捕获同步上下文,并在Post和Send方法中使用Begin/INQUE

当您编写自己的SynchronizationContext时,现在必须注意这些细节。在代码段中,您没有覆盖Post和Send,而是继承了基本方法。他们什么都不知道,只能在任意线程池线程上执行请求。因此SynchronizationContext.Current现在在该线程上为null,线程池线程不知道请求来自何处

创建自己的并不困难,ConcurrentQueue和委托可以大大减少代码。很多程序员都这样做过,这经常被引用。但要付出沉重的代价,dispatcher循环从根本上改变了控制台模式应用程序的行为方式。它会阻塞线程,直到循环结束。就像Application.Run()一样

您需要一种非常不同的编程风格,这种风格是您在GUI应用程序中熟悉的。代码不会花费太长时间,因为它会阻塞调度程序循环,从而阻止调用请求被调度。在GUI应用程序中,UI变得无响应非常明显,在您的示例代码中,您会注意到您的方法完成得很慢,因为延续程序暂时无法运行。您需要一个工作线程来派生慢代码,没有免费的午餐


值得注意的是,为什么会存在这种东西。GUI应用程序有一个严重的问题,它们的类库从来都不是线程安全的,也不能通过使用
lock
来保证安全。正确使用它们的唯一方法是从同一线程进行所有调用。无效操作异常,如果您没有。他们的dispatcher循环帮助您完成这项工作,为Begin/Invoke和async/await供电。控制台没有这个问题,任何线程都可以向控制台写入内容,锁可以帮助防止它们的输出混淆。因此,控制台应用程序不需要自定义SynchronizationContext。YMMV.

:首先尝试获取当前同步上下文。如果当前上下文实际上只是基本SynchronizationContext类型,这相当于根本没有当前SynchronizationContext,那么忽略它。通过避免不必要的发布和工作项排队,这有助于提高性能,但更重要的是,它确保了如果代码碰巧将默认上下文发布为当前上下文,则不会阻止使用当前任务调度程序(如果有).P.S。您的
Post
实现不正确。它必须确保在适当的上下文中调用委托
base.Post
不适合您。@PetSerAl“如果当前上下文实际上只是基本同步上下文类型…”,但
MySC
不是基本SC类型;表示
syncCtx.GetType()!=typeof(SynchronizationContext)
为true。@PetSerAl和yes我的
帖子不正确。。这只是一个用于学习目的的假实现:)正如您所说,您的
Post
被调用。这就是上下文捕获结束的地方。它只负责对捕获的上下文调用
Post
,而不负责其他操作。其他任何内容都是
Post
i