Task.Run在这里是否合适,包装网络呼叫?

Jim*_*ica 1 c# asynchronous threadpool async-await

我正在研究一种非常流行的c#代理服务器.在其中有代码如(我故意隐藏代理服务器身份,因为我觉得很难讲故事,如果确实是错误的话).

private void OnConnectionAccepted(TcpClient cl)
{
     ....
     Task.Run(async () =>
            {
               await HandleClientRequest();

            });
     Task.Run(async () =>
        {
            TcpClient cl = await listener.AcceptTcpClientAsync();
            OnConnectionAccepted(cl);
        });
}
Run Code Online (Sandbox Code Playgroud)

HandleClientRequest某些异步网络调用在哪里,例如从客户端读取请求并将其重复发送到服务器,就像您希望代理一样. listener.AcceptTcpClientAsync只是等待来自浏览器的下一个连接.

所以看起来Task.Run的重点只是允许AcceptTcpClientAsyncHandleClientRequest忙碌时被调用.

这似乎违背了我一直在阅读的建议,例如.http://blog.stephencleary.com/2013/10/taskrun-etiquette-and-proper-usage.html因为Task.Run正在包装网络,而不是CPU调用.

那么它应该如何工作呢,如果这是错的,它应该使用ThreadPool.QueueUserWorkerItem吗?

顺便说一句,上下文通常是一个控制台应用程序.

Ste*_*ary 5

这似乎违背了我一直在阅读的建议...因为Task.Run包装网络,而不是CPU调用.

通常,Task.Run不应该用于I/O. 但是,该建议适用于应用程序代码,甚至有几个例外.

在这种情况下(检查代理服务器的核心接受/处理逻辑),我们正在查看的代码更多的是框架,其中"应用程序"在内部HandleClientRequest.(请注意,此代码托管在Console或Win32服务中,而不是在ASP.NET中).

为了绘制并行,ASP.NET将侦听连接,并从线程池中获取一个线程来处理请求.这对于框架来说是完全自然的.

以下是使用的一些原因Task.Run,对于此特定情况可能有效,也可能无效:

  • 某些.NET网络调用以同步部分开始 - 特别是HTTP代理和DNS查找是同步完成的.这是非常不幸的,但出于向后兼容的原因,我们坚持使用它.因此,即使异步网络API也可以部分同步.如果HandleClientRequest使用这些API,将它包装在一个中是有意义的Task.Run.
  • 代码可能不想要当前SynchronizationContext.但是,这似乎并非如此.
  • 所有异步方法都开始同步执行.如果HandleClientRequest在它开始实际工作之前有一些"管家",那么将它包装成一个Task.Run听众没有被阻挡可能是有益的.
  • 如果同步执行,递归异步代码可以填充堆栈.例如,如果第二个Task.Run不存在并且同时进行了大量连接,那么堆栈可能会溢出.我认为这是第二个目的,Task.Run因为我不能想到它会有任何其他目的.

那么它应该如何工作呢,如果这是错的,它应该使用ThreadPool.QueueUserWorkerItem吗?

绝对不!Task.Run是使用线程池的适当方法.(除非你有一个非常聪明的团队,你可以显示出可测量的性能差异).

因此,似乎Task.Run的目的只是允许在HandleClientRequest繁忙时调用AcceptTcpClientAsync.

的使用Task.Run仍然是问题的最好,因为任务早晚期待.当前标准的,从任何异常HandleClientRequest,AcceptTcpClientAsync以及OnConnectionAccepted将会被丢弃.如果HandleClientRequestOnConnectionAccepted两个有顶级try块,那将是美好的,但任何的异常AcceptTcpClientAsync会导致整个代理服务器停止工作.并且该方法可以抛出异常,尽管很少见.