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

查看:158
本文介绍了为什么在控制台应用程序中未捕获默认的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.

但是,在我的应用程序中,SynchronizationContext.Currentawait之后为null.为什么会这样?

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");
    }
}

这就是我的用法:

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 and 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. YMMV.

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天全站免登陆