为什么在控制台应用程序中未捕获默认 SynchronizationContext? [英] Why the default SynchronizationContext is not captured in a Console App?

查看:20
本文介绍了为什么在控制台应用程序中未捕获默认 SynchronizationContext?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试了解有关 SynchronizationContext 的更多信息,因此我制作了这个简单的控制台应用程序:

I'm trying to learn more about the SynchronizationContext, so I made this simple console application:

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,然后将其余的异步方法发送给它.

If I understand correctly, the await operator captures the current SynchronizationContext then posts the rest of the async method to it.

但是,在我的应用程序中,await 之后的 SynchronizationContext.Current 为空.这是为什么?

However, in my application the SynchronizationContext.Current is null after the await. Why is that ?

即使我使用自己的 SynchronizationContext 它也不会被捕获,尽管它的 Post 函数被调用.这是我的 SC:

Even when I use my own SynchronizationContext it is not captured, although its Post function is called. Here is my SC:

public class MySC : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        base.Post(d, state);
        Console.WriteLine("Posted");
    }
}

这就是我使用它的方式:

And this is how I use it:

var sc = new MySC();
SynchronizationContext.SetSynchronizationContext(sc);

谢谢!

推荐答案

捕获"这个词太不透明了,听起来太像框架应该做的事情了.误导,因为它通常在使用默认 SynchronizationContext 实现之一的程序中执行.像 您在 Winforms 应用程序中获得的一个.但是当你自己编写框架时,框架就不再有用了,你的工作就变成了.

The word "capture" is too opaque, it sounds too much like that is something that the framework is supposed to. Misleading, since it normally does in a program that uses one of the default SynchronizationContext implementations. Like the one you get in a Winforms app. But when you write your own then the framework no longer helps and it becomes your job to do it.

异步/等待管道使上下文有机会在特定线程上运行延续(等待之后的代码).这听起来是一件微不足道的事情,因为您以前经常这样做,但实际上非常困难.不可能任意中断该线程正在执行的代码,这会导致可怕的重入错误.该线程必须提供帮助,它需要解决标准的生产者-消费者问题.接受一个线程安全队列和一个清空该队列的循环,处理调用请求.被重写的 Post 和 Send 方法的工作是将请求添加到队列中,线程的工作是使用循环来清空它并执行请求.

The async/await plumbing gives the context an opportunity to run the continuation (the code after the await) on a specific thread. That sounds like a trivial thing to do, since you've done it so often before, but it is in fact quite difficult. It is not possible to arbitrarily interrupt the code that this thread is executing, that would cause horrible re-entrancy bugs. The thread has to help, it needs to solve the standard producer-consumer problem. Takes a thread-safe queue and a loop that empties that queue, handling invoke requests. The job of the overridden Post and Send methods is to add requests to the queue, the job of the thread is to use a loop to empty it and execute the requests.

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

The main thread of a Winforms, WPF or UWP app has such a loop, it is executed by Application.Run(). With a corresponding SynchronizationContext that knows how to feed it with invoke requests, respectively WindowsFormsSynchronizationContext, DispatcherSynchronizationContext and WinRTSynchronizationContext. ASP.NET can do it too, uses AspNetSynchronizationContext. All provided by the framework and automagically installed by the class library plumbing. They capture the sync context in their constructor and use Begin/Invoke in their Post and Send methods.

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

When you write your own SynchronizationContext then you must now take care of these details. In your snippet you did not override Post and Send but inherited the base methods. They know nothing and can only execute the request on an arbitrary threadpool thread. So SynchronizationContext.Current is now null on that thread, a threadpool thread does not know where the request came from.

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

Creating your own isn't that difficult, ConcurrentQueue and delegates help a lot of cut down on the code. Lots of programmers have done so, this library is often quoted. But there is a severe price to pay, that dispatcher loop fundamentally alters the way a console mode app behaves. It blocks the thread until the loop ends. Just like Application.Run() does.

您需要一种非常不同的编程风格,您可以从 GUI 应用程序中熟悉这种风格.代码不能花费太长时间,因为它会干扰调度程序循环,阻止调用请求被调度.在 GUI 应用程序中,UI 变得无响应非常明显,在您的示例代码中,您会注意到您的方法完成速度很慢,因为延续无法运行一段时间.你需要一个工作线程来分拆慢代码,天下没有免费的午餐.

You need a very different programming style, the kind that you'd be familiar with from a GUI app. Code cannot take too long since it gums up the dispatcher loop, preventing invoke requests from getting dispatched. In a GUI app pretty noticeable by the UI becoming unresponsive, in your sample code you'll notice that your method is slow to complete since the continuation can't run for a while. You need a worker thread to spin-off slow code, there is no free lunch.

值得一提的是为什么这些东西存在.GUI 应用程序有一个严重的问题,它们的类库从来都不是线程安全的,也不能通过使用 lock 来确保安全.正确使用它们的唯一方法是从同一线程进行所有调用.InvalidOperationException 当你不这样做时.他们的调度程序循环可以帮助您做到这一点,为 Begin/Invoke 和 async/await 提供支持.控制台没有这个问题,任何线程都可以向控制台写入一些东西,锁可以帮助防止它们的输出混合在一起.因此,控制台应用程序不需要自定义 SynchronizationContext.天啊.

Worthwhile to note why this stuff exists. GUI apps have a severe problem, their class libraries are never thread-safe and can't be made safe by using lock either. The only way to use them correctly is to make all the calls from the same thread. InvalidOperationException when you don't. Their dispatcher loop help you do this, powering Begin/Invoke and async/await. A console does not have this problem, any thread can write something to the console and lock can help to prevent their output from getting intermingled. So a console app shouldn't need a custom SynchronizationContext. YMMV.

这篇关于为什么在控制台应用程序中未捕获默认 SynchronizationContext?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆