异步网络服务器和垃圾收集
Asynchronous webserver and garbage collection
在阅读了 Stephen Cleary 关于异步的 article 和 asp.net 之后,很明显在 Web 应用程序中使用异步在可伸缩性方面是一个巨大的胜利(主要是由于免费的非阻塞线程来满足更多请求)。
虽然我对异步操作返回的任务对象如何保留在 Aysnc WebServer 实现的范围内感到有点困惑(假设是 iis 或自托管 WebApi)
而不会被收集。
例如,
如果我们在低级别 Web 服务器实现中有以下方法,
// some method that handles HttpListeners BeginContextAsync callback,
public void SomeHttpListenerCallback(HttpListenerContext context)
{
// Immediately set up the next begin context
// then handle the request
// forgive me if this is incorrect but this is my understanding of how an async request would be handled
// please feel free to point out the flaws
var task = messageHanlder1.SendAsync(context.Request, null);
task.ContinueWith(t => SendResponse(t));
}
public class MessageHandler1 : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Process request");
// Call the inner handler.
var response = await base.SendAsync(request, cancellationToken);
Debug.WriteLine("Process response");
return response;
}
}
SendResponse
是将响应发送到客户端套接字的某种方法。
您可能已经注意到,任务对象在 SomeHttpListenerCallback
中超出了范围,因为没有其他线程(理论上)对任务对象有任何引用,它不会被标记为在下一个 GC 周期中收集吗?
我已经阅读了 Jeffrey Ritcher 关于编译器如何将 aysnc 方法转换为带有状态机的方法的解释(通过 C# 的 Clr),但是我无法理解如果没有线程等待任务对象会发生什么。
如果一个线程等待它,我们不会为每个请求阻塞 1 个线程吗?
任何帮助我指明正确方向的帮助将不胜感激。
对于您执行的每个异步操作,其背后都有一些 "native" 操作 - 套接字读取或文件 IO 等。这些是本机 CLR 操作或 PInvoke 调用。当开始这样的操作时,框架会注册一个回调以在完成时调用。此回调用于完成任务。该回调由 "the system" 保持活动状态。例如,当您说 myFileStream.BeginRead(myCallback)
时,回调 将被调用 ,即使它未被引用。
简而言之,本机操作本身使完成时必须处理的所有内容保持活动状态。
这不是硬性规定,但如果 IO 框架只是有时调用您的回调,那么它就毫无用处。
没有什么可以阻止 any Task
被收集。例如var dummy = new TaskCompletionSource<object>().Task;
立即符合领取条件。
首先,如果您有一个 Task
并且没有任何引用它,那么它将被 GC
收集。
然而,几乎从来没有这种情况。有 2 种类型的任务,Promise 任务和 Delegate 任务。
Promise 任务(异步)主要由 TaskCompletionSource
或 async
方法的编译器创建。在这两种情况下,有人持有对任务的引用,因此它能够完成它。
然而,委托任务(同步)由在其中执行委托的线程引用。
在你的例子中,task
被 SendAsync
后面的状态机引用,直到它完成,而 task
正在引用延续 Task
。当它完成时,延续被调度,因此被 TaskScheduler
和执行的线程 SendResponse
引用。操作完成后,任务将不再被引用,最终可能会 GC
ed.
您可以在 implementation of Task.Delay
中看到一个示例,其中 Task
(实际上 DelayPromise
继承自 Task
)被 System.Threading.Timer
引用用于完成 Task
.
因为有些参数实际上不需要计时器,所以您可以看到这两者在内存使用方面的区别:
static void Main()
{
while (true)
{
Task.Delay(int.MaxValue);
}
}
还有这个:
static void Main()
{
while (true)
{
Task.Delay(-1); // No Timer as the delay is infinite.
}
}
在阅读了 Stephen Cleary 关于异步的 article 和 asp.net 之后,很明显在 Web 应用程序中使用异步在可伸缩性方面是一个巨大的胜利(主要是由于免费的非阻塞线程来满足更多请求)。
虽然我对异步操作返回的任务对象如何保留在 Aysnc WebServer 实现的范围内感到有点困惑(假设是 iis 或自托管 WebApi) 而不会被收集。
例如,
如果我们在低级别 Web 服务器实现中有以下方法,
// some method that handles HttpListeners BeginContextAsync callback,
public void SomeHttpListenerCallback(HttpListenerContext context)
{
// Immediately set up the next begin context
// then handle the request
// forgive me if this is incorrect but this is my understanding of how an async request would be handled
// please feel free to point out the flaws
var task = messageHanlder1.SendAsync(context.Request, null);
task.ContinueWith(t => SendResponse(t));
}
public class MessageHandler1 : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Process request");
// Call the inner handler.
var response = await base.SendAsync(request, cancellationToken);
Debug.WriteLine("Process response");
return response;
}
}
SendResponse
是将响应发送到客户端套接字的某种方法。
您可能已经注意到,任务对象在 SomeHttpListenerCallback
中超出了范围,因为没有其他线程(理论上)对任务对象有任何引用,它不会被标记为在下一个 GC 周期中收集吗?
我已经阅读了 Jeffrey Ritcher 关于编译器如何将 aysnc 方法转换为带有状态机的方法的解释(通过 C# 的 Clr),但是我无法理解如果没有线程等待任务对象会发生什么。
如果一个线程等待它,我们不会为每个请求阻塞 1 个线程吗? 任何帮助我指明正确方向的帮助将不胜感激。
对于您执行的每个异步操作,其背后都有一些 "native" 操作 - 套接字读取或文件 IO 等。这些是本机 CLR 操作或 PInvoke 调用。当开始这样的操作时,框架会注册一个回调以在完成时调用。此回调用于完成任务。该回调由 "the system" 保持活动状态。例如,当您说 myFileStream.BeginRead(myCallback)
时,回调 将被调用 ,即使它未被引用。
简而言之,本机操作本身使完成时必须处理的所有内容保持活动状态。
这不是硬性规定,但如果 IO 框架只是有时调用您的回调,那么它就毫无用处。
没有什么可以阻止 any Task
被收集。例如var dummy = new TaskCompletionSource<object>().Task;
立即符合领取条件。
首先,如果您有一个 Task
并且没有任何引用它,那么它将被 GC
收集。
然而,几乎从来没有这种情况。有 2 种类型的任务,Promise 任务和 Delegate 任务。
Promise 任务(异步)主要由 TaskCompletionSource
或 async
方法的编译器创建。在这两种情况下,有人持有对任务的引用,因此它能够完成它。
然而,委托任务(同步)由在其中执行委托的线程引用。
在你的例子中,task
被 SendAsync
后面的状态机引用,直到它完成,而 task
正在引用延续 Task
。当它完成时,延续被调度,因此被 TaskScheduler
和执行的线程 SendResponse
引用。操作完成后,任务将不再被引用,最终可能会 GC
ed.
您可以在 implementation of Task.Delay
中看到一个示例,其中 Task
(实际上 DelayPromise
继承自 Task
)被 System.Threading.Timer
引用用于完成 Task
.
因为有些参数实际上不需要计时器,所以您可以看到这两者在内存使用方面的区别:
static void Main()
{
while (true)
{
Task.Delay(int.MaxValue);
}
}
还有这个:
static void Main()
{
while (true)
{
Task.Delay(-1); // No Timer as the delay is infinite.
}
}