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

时间:2014-01-09 09:05:25

标签: c# .net multithreading http asynchronous

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

httpListener.BeginGetContext(new AsyncCallback(ListenerCallback), httpListener);

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

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

189     HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
190     HttpWebResponse response = (HttpWebResponse)request.GetResponse();

现在,如果AsyncCallback调度新线程并在该新线程中运行ListenerCallback,则在通过诊断发送虚拟请求时,它不得结果Operation Timeout。这就是我认为理想的行为,因为它是*Async*Callback。实际上MSDN也是says the same

  

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

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

以视觉方式解释:

enter image description here

3 个答案:

答案 0 :(得分:14)

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

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

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

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

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

答案 1 :(得分:6)

  

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

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

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

  

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

我相信这意味着你应该在调用回调的线程上处理异步操作的结果。这个线程几乎总是与启动操作的线程分开,如上所述。这也是他们sample code显然所做的事情。请注意,它们不会在ProcessDnsInformation内创建任何新线程。

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

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

但是,使用基于Task Parallel Library (TPL)async/await patternTask的API(如HttpListener.GetContextAsync),这是一块蛋糕。最好的部分:你不必再担心线程了。

为了让您了解我在说什么,这里是an example of a low-level TCP server。在实现基于HttpListener的HTTP服务器时,可以使用非常相似的概念。

答案 2 :(得分:2)

添加汉斯的优秀答案,

即使执行异步操作,它实际上也可以同步完成 - 即使在方案#2中,也不会说回调会在另一个线程上返回。依靠这种行为实际上最终可能会导致死锁或溢出堆栈。

您可以查看IAsyncResult.CompletedSynchronously属性以确定此问题。设置为true时,完成回调很可能是在启动异步操作的同一线程上进行的:回调将在Begin*调用期间运行,并且必须在它返回之前完成。