我正在研究一种非常流行的c#代理服务器。在其中有代码,例如(我故意隐藏代理服务器身份,因为我觉得很难讲故事,如果确实是错误的话)。
private void OnConnectionAccepted(TcpClient cl)
{
....
Task.Run(async () =>
{
await HandleClientRequest();
});
Task.Run(async () =>
{
TcpClient cl = await listener.AcceptTcpClientAsync();
OnConnectionAccepted(cl);
});
}
HandleClientRequest
执行某些异步网络调用,例如从客户端读取请求并将其重复发送到服务器,就像您希望代理一样。
listener.AcceptTcpClientAsync
只是等待来自浏览器的下一个连接。
所以看起来Task.Run的重点只是允许在AcceptTcpClientAsync
忙碌时调用HandleClientRequest
。
这似乎违背了我一直在阅读的建议,例如。 http://blog.stephencleary.com/2013/10/taskrun-etiquette-and-proper-usage.html因为Task.Run正在包装网络,而不是CPU调用。
那么它应该如何工作,如果这是错误的,它应该使用ThreadPool.QueueUserWorkerItem吗?
顺便说一句,上下文通常是一个控制台应用程序。
答案 0 :(得分:5)
这似乎违背了我一直在阅读的建议......因为Task.Run包装网络,而不是CPU调用。
通常,Task.Run
不应用于I / O.但是,该建议仅适用于应用程序代码,甚至有几个例外。
在这种情况下(检查代理服务器的核心接受/处理逻辑),我们正在查看的代码更像是一个框架,其中“application”位于{{1 }}。 (请注意,此代码托管在Console或Win32服务中,而不在ASP.NET中)。
要绘制并行,ASP.NET将侦听连接,并从线程池中获取一个线程来处理请求。这对框架来说非常自然。
以下是使用HandleClientRequest
的一些原因,这些原因可能适用于此特定情况,也可能不适用:
Task.Run
使用这些API,则将其包装在HandleClientRequest
中是有意义的。Task.Run
。但是,这似乎不是这种情况。SynchronizationContext
在开始实际工作之前做了一些“管家”,那么将它包装在HandleClientRequest
中可能是有益的,这样听众就不会被阻止。Task.Run
不存在且一次连接的批次,那么堆栈可能会溢出。我认为这是第二个Task.Run
的目的,因为我无法想到它会有任何其他目的。那么它应该如何工作,如果这是错误的,它应该使用ThreadPool.QueueUserWorkerItem吗?
绝对不是! Task.Run
是使用线程池的适当方式。 (除非你有一个非常聪明的团队和,否则你可以显示出可测量的性能差异。)
所以看起来Task.Run的目的只是允许在HandleClientRequest忙的时候调用AcceptTcpClientAsync。
Task.Run
的用法充其量仍然值得怀疑,因为迟早要等待 的任务。目前的情况是,Task.Run
,HandleClientRequest
和AcceptTcpClientAsync
的所有例外情况都会被默默删除。如果OnConnectionAccepted
和HandleClientRequest
都有顶级OnConnectionAccepted
块,那就没问题,但try
的任何异常都会导致整个代理服务器停止工作。并且该方法可以抛出异常,尽管很少见。
答案 1 :(得分:1)
我建议将这一点重构为一个接受连接的方法和另一个处理传入数据的方法。最后,第三个处理失败的任务。
public async Task Acceptor(TcpListener listener)
{
for (;;)
{
TcpClient client = await listener.AcceptTcpClientAsync();
OnConnectionAccepted(client).ContinueWith(task => { errorHandling }, OnlyOnFaulted);
// some logic which will stop this loop if necessary
}
}
private async Task OnConnectionAccepted(TcpClient client)
{
await HandleClientRequest(client);
// .. further logic
}
当侦听器不再接受新连接时,“Acceptor”将抛出。当无法处理某个特定连接时,将调用“errorHandling”。 (您也可以考虑使用OnConnectionAccepted中的try catch并使其异步无效 - 我个人不喜欢这样做)
Stephen是对的,几乎没有任何使用Task.Run的情况。这也是因为async / await和Task.Run在不同的抽象级别上工作。除非您正在编写某种类型的库async / await应该总是足够。