C#AsyncCallback是否创建了一个新线程?

Mah*_*aha 14 .net c# multithreading asynchronous http

我写了一个HttpListener侦听其中一个端口:

httpListener.BeginGetContext(new AsyncCallback(ListenerCallback), httpListener);
Run Code Online (Sandbox Code Playgroud)

ListenerCallback处理是对听众的URI接收到的任何请求.如果在处理请求期间发生异常,它将运行一个诊断例程,该例程尝试命中侦听器uri以检查侦听器是否实际处于活动状态并侦听uri并写入侦听器返回的响应日志.Listener只是将字符串返回Listening...给这样的虚拟请求.

现在在测试期间,当导致执行诊断模块的其他模块发生异常时,我可以看到在Listening...检查日志时监听器正确返回.但是,当发生异常时ListenerCallback,尝试在诊断内部命中侦听器URI会引发以下异常:

System.Net.WebException : The operation has timed out
   at System.Net.HttpWebRequest.GetResponse()
   at MyPackage.Diagnostics.hitListenerUrl(String url) in c:\SW\MyApp\MyProj\Diagnostics.cs:line 190
Run Code Online (Sandbox Code Playgroud)

诊断模块中的第190行如下:

189     HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
190     HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Run Code Online (Sandbox Code Playgroud)

现在,如果AsyncCallback调度新线程并ListenerCallback在该新线程中运行,则Operation Timeout在通过诊断发送虚拟请求时,不得产生该线程.这就是我认为理想的行为,因为它是*Async*Callback.事实上MSDN也说同样的话:

使用AsyncCallback委托在单独的线程中处理异步操作的结果.

但似乎并非如此.我在这里错过了什么吗?

视觉解释:

在此输入图像描述

Han*_*ant 14

它完全是类'BeginXxx()方法的实现细节.有两种基本方案:

  • BeginXxx()启动一个线程来完成工作,该线程进行回调
  • BeginXxx()要求操作系统完成工作,使用I/O完成端口要求在完成后通知.操作系统启动一个线程来传递运行回调的通知.

第二种方法非常理想,它可以很好地扩展,程序可以有许多待处理的操作.并且是HttpListener使用的方法,Windows上的TCP/IP驱动程序堆栈支持完成端口.您的程序可以轻松支持数千个套接字,在服务器方案中非常重要.

回调中的EndXxx()调用报告在尝试通过抛出异常来完成I/O请求时遇到的任何错误.在您的情况下,BeginGetContext()需要回调中的EndGetContext().如果您没有捕获异常,那么您的程序将终止.

您的代码段实际上并未演示任何异步I/O. 您调用了GetResponse()而不是BeginGetResponse().根本不涉及回调,因此将失败并抛出异常的GetResponse()方法.


nos*_*tio 6

C#AsyncCallback是否创建了一个新线程?

不,它本身不是,它只是一个回调.但是,调用回调的代码可能是在与原始操作启动的线程不同的线程上调用它(在您的情况下,操作是httpListener.BeginGetContext).

通常(但不是必需),它在随机ThreadPool线程上调用,该线程碰巧处理底层套接字IO操作的完成(更多细节,这里是一个很好的读取:没有线程).

使用AsyncCallback委托在单独的线程中处理异步操作的结果.

我相信这意味着您应该在调用回调的线程上处理异步操作的结果.如上所述,该线程几乎总是与启动操作的线程分开.这也是他们的示例代码显然所做的.请注意,他们不会在里面创建任何新线程ProcessDnsInformation.

收到回调后,由您决定如何组织服务器应用程序的线程模型.主要关注的是,它应该保持响应和可扩展性.理想情况下,您应该在它到达的同一线程上提供传入请求,并在完成处理请求所需的任何CPU绑定作业后立即释放此线程.作为处理逻辑的一部分,您可能需要执行其他IO绑定任务(访问文件,执行数据库查询,调用Web服务等).在这样做时,您应尽可能使用相关API的异步版本,以避免阻塞请求线程(再次,请参阅"没有线程").

IMO,使用您选择的异步编程模型(APM)模式,实现此类逻辑可能是一项相当繁琐的任务(尤其是错误处理和恢复部分).

但是,使用任务并行库(TPL),基于async/await模式Task基于API的API HttpListener.GetContextAsync,它是一块蛋糕.最好的部分:你不必再担心线程了.

为了让您了解我在说什么,这里是一个低级TCP服务器的示例.在实现HttpListener基于HTTP的服务器时,可以使用非常类似的概念.