C# · 12月 22, 2021

c# – asp.net核心和kestrel线程池中的异步处理

我是ASP.NET Core和C#的新手,一般来自 Java世界,我对async / await关键字如何工作有点困惑: This article解释说如果一个方法标记为异步,则意味着

“this method contains control flow that involves awaiting asynchronous operations and will therefore be rewritten by the compiler into continuation passing style to ensure that the asynchronous operations can resume this method at the right spot.” The whole point of async methods it that you stay on the current thread as much as possible

所以我认为在异步方法和在字节码/虚拟机级别上实现的同一线程上运行的调用者之间传递控制是一种非常酷的方式.我的意思是我期望下面的代码

public static void Main(string[] args){ int delay = 10; var task = doSthAsync(delay); System.Console.WriteLine(“main 1: ” + Thread.CurrentThread.ManagedThreadId); // some synchronous processing longer than delay: Thread.Sleep(2 * delay) System.Console.WriteLine(“main 2: ” + Thread.CurrentThread.ManagedThreadId); task.Wait();}public static async Task<String> doSthAsync(int delay){ System.Console.WriteLine(“async 1: ” + Thread.CurrentThread.ManagedThreadId); await Task.Delay(delay); System.Console.WriteLine(“async 2: ” + Thread.CurrentThread.ManagedThreadId); return “done”;}

会写的

async 1: 1main 1: 1main 2: 1async 2: 1

通过使用await关键字将控制从doSthAsync传递给Main,然后使用task.Wait()返回doSthAsync(并且所有这些都发生在单个线程上).

但是上面的代码实际上是打印的

async 1: 1main 1: 1async 2: 4main 2: 1

这意味着它只是一种将“异步”方法委托给单独线程的方法,如果当前线程忙,这几乎与上述文章所述的相反:

The “async” modifier on the method does not mean “this method is automatically scheduled to run on a worker thread asynchronously”

因此,由于异步方法的“延续”显然是或者至少可以安排在不同的线程上运行,因此我有一些问题似乎是C#应用程序可伸缩性的基础:

每个异步方法是否可以在新生成的线程上运行,或者C#运行时是否为此目的维护特殊的线程池,在后一种情况下,如何控制此池的大小?
更新:感谢@Servy我现在知道,对于CMD-line应用程序,它将是一个线程池线程,但我仍然不知道如何控制这些线程池线程的数量).

在ASP.NET Core的情况下:如何控制Kestrel用于执行异步实体框架操作或其他(网络)I / O的线程池的大小?它是否与用于接受HTTP请求的线程池相同?
更新:感谢Stephen Cleary的this和this article我现在明白它默认会运行“单线程”,即:在任何给定时间,在给定HTTP请求的SynchronizationContext中将只有一个线程,但是当某些异步操作被标记为已完成且任务恢复时,此线程可能会切换到另一个.我还了解到,可以通过使用ConfigureAwait(false)将任何异步方法“从给定的SynchronizationContext”中调度到线程池线程.不过,我不知道如何控制线程池线程的数量,以及它是否与Kestrel用于接受HTTP请求的池相同.

据我所知,如果所有I / O都是异步完成的,那么这些池的大小(无论是实际上只有1个线程池还是2个独立的)都与打开到外部资源的传出连接数没有任何关系像DB,对吗? (运行异步代码的线程将继续接受新的HTTP请求,并且由于它们的处理将打开传出连接(到DB或某些Web服务器以从Web获取某些资源),尽可能快地进行,而不会有任何阻塞)
这反过来意味着如果我不想杀死我的数据库,我应该设置其连接池的合理限制,并确保等待可用连接也是异步完成的,对吧?
假设我是正确的,我仍然希望能够限制线程池线程的数量,以防一些长时间的I / O操作被错误地同步进行,以防止由于大量的线程而产生大量线程线程在这个同步操作上被阻塞(即:我认为最好限制我的应用程序的性能,而不是因为这样的bug而导致它产生疯狂的线程数)

我也有“只是出于好奇”的问题:为什么实际上没有实现async / await来在同一个线程上执行异步任务?我不知道如何在Windows上实现它,但在Unix上,这可以使用select / poll系统调用或信号来实现,所以我相信有一种方法可以在Windows上实现这一点,它将是一个非常酷的语言功能确实.
更新:如果我理解正确的话,如果我的SynchronizationContext不为null,那么事情将会起作用:即代码将运行“kind-of-single-threadly”:在任何给定时间,给定的SynchronizationContext中将只有一个线程.但是它的实现方式会使同步方法等待异步任务死锁,对吧? (运行时会将任务标记为已完成,以便在等待完成此任务的同一线程上运行)

谢谢!

解决方法 await使用SynchronizationContext.Current的值来安排延续.在控制台应用程序中,默认情况下没有当前的同步上下文;所以它只是使用线程池线程来安排延续.在具有消息循环的应用程序(例如winforms,WPF或winphone应用程序)中,消息循环将当前同步上下文设置为将所有消息发送到消息循环的上下文,从而在UI线程上运行它们.

在ASP应用程序中,还会有一个当前的同步上下文,但它不是一个特定的线程.相反,当需要为同步上下文运行下一条消息时,它需要一个线程池线程,使用正确请求的请求数据进行设置,然后让它运行该消息.这意味着在ASP应用程序中使用同步上下文时,您知道一次只能运行多个操作,但它不一定是处理每个响应的单个线程.