asp.net核心和kestrel线程池

时间:2016-12-12 13:18:00

标签: c# asp.net-core async-await

我是新的ASP.NET Core和C#,一般来自Java世界,我对async / await关键字如何工作感到困惑:This article解释了如果一个方法标记为异步,表示

  

"此方法包含涉及等待异步操作的控制流,因此将由编译器重写为延续传递样式,以确保异步操作可以在正确的位置恢复此方法。“整个你可以尽可能多地使用异步方法

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

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: 1
main 1: 1
main 2: 1
async 2: 1

将控件从doSthAsync传递到Main,然后使用await关键字,然后使用doSthAsync返回task.Wait()(所有这些都发生在一个线程上)。

然而,上面的代码实际上打印

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

这意味着它只是一种委托&#34; async&#34;如果当前线程正忙,则该方法为一个单独的线程,这与所提到的文章几乎完全相同

  

方法上的“async”修饰符并不意味着“此方法会自动安排在工作线程上异步运行”

因此,&#34;延续&#34;异步方法显然 或至少可能计划在不同的线程上运行,我有一些问题似乎是C#应用程序可伸缩性的基础:

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

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

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

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

谢谢!

3 个答案:

答案 0 :(得分:10)

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

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

答案 1 :(得分:5)

默认情况下,当没有SynchronizationContext或在等待任务上调用ConfigureAwait(false)时,其继续将在CLR维护的线程池上运行,可以使用ThreadPool class中的静态方法访问该线程池。 来自ThreadPool的.net核心方法来控制其大小(setMaxThreadssetMinThreads)尚未实现:
https://github.com/dotnet/cli/issues/889
https://github.com/dotnet/corefx/issues/5920
但是,可以在project.json文件中静态设置其大小,如下所述:
https://github.com/dotnet/cli/blob/rel/1.0.0/Documentation/specs/runtime-configuration-file.md#runtimeoptions-section-runtimeconfigjson

kestrel有自己的池,用于使用libuv异步处理请求:它由KestrelServerOptionsstatic ThreadCount property控制。

相关文章:
http://blog.stephencleary.com/2012/02/async-and-await.html
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/
https://msdn.microsoft.com/en-us/magazine/gg598924.aspx

请致@Servy和@Damien_The_Unbeliever以获取允许我找到它的提示

答案 2 :(得分:0)

这里值得一提,因为问题是关于 .net net 的,与 .Net 不同的是,不是 {strong}点网核心中的{1}}

您的异步任务将使用线程池的任何线程运行。

SynchronizationContext无关紧要,除非您认为它将在.Net中使用

请参阅 https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html

ConfigureAwait(false) relevant in ASP.NET Core?