有一个方法:
public async Task StartListeningAsync(CancellationToken stoppingToken)
{
_listener.Start();
while (!stoppingToken.IsCancellationRequested)
{
var client = await _listener.AcceptTcpClientAsync();
var thread = new Thread(async () => await ProcessClientAsync(client, stoppingToken));
thread.Start();
}
}
这_listener是标准类的对象TcpListener。任务是在单独的线程中处理单独的客户端。客户端处理可能需要很长时间才能断开连接(例如 5 天)。客户端处理方法ProcessClientAsync是异步的(原则上可以同步)。我需要在单独的线程上运行此方法。目前我通过new Thread. 有人告诉我,在await方法内部的第一个之后,ProcessClientAsync线程会将控制权返回给外部,导致处理线程终止(这在此处进行了讨论)。
还有另一种选择Task.Run:
_listener.Start();
while (!stoppingToken.IsCancellationRequested)
{
var client = await _listener.AcceptTcpClientAsync();
Task.Run(() => ProcessClientAsync(client, stoppingToken), stoppingToken);
}
但是编译器会警告这个选项:
警告 CS4014 由于不等待此调用,因此在调用完成之前继续执行当前方法。考虑将“等待”运算符应用于调用结果。
问题:ProcessClientAsync在我的情况下,在单独的线程中运行客户端处理方法如何正确?该方法最好保持异步。如果不能保持异步,请解释原因。
停止在流中思考!不,不是这样……
停止在流中思考!
在您的问题中,您从异步方法在特定线程中执行的事实着手,并且您需要更改此线程。但事实是,大多数时候你的异步方法并没有在任何线程上执行!
典型的异步客户端处理代码是如何构成的?或多或少是这样的:
大多数时候(从问题开始的同样的 5 天)你只是等待数据从套接字到达。从代码的角度来看,这是最“等待”的——它只是系统表中某处的一条记录,当数据到达时,需要通知您。没有线程被等待阻塞。您不能在单独的线程中等待来自客户端的数据,因为没有线程。
当数据到达时,您的方法的执行无论如何都会在线程池中继续执行(有编写自己的任务调度程序的选项 - 但您不太可能编写比系统更好的调度程序)。
现在谈谈有时会吓到初学者的长任务,不要长时间占用线程池。因此,“长任务”是处理主动计算的任务。您的 5 天池中没有一个线程处于忙碌状态,因此这
ProcessClientAsync不是一项漫长的任务。但ProcessRequest在极少数情况下,它可能会变成“长”。如果发生这种情况,您可以使用以下结构告诉系统任务调度程序:Task.Factory.StartNew请注意,传递给闭包的内部没有运算符await。这就是它应该的方式。如果你需要在里面写await,那么你错误地选择了一个“长”任务!最后,关于
ProcessClientAsync无需等待即可启动。Captain Evidence 建议不等待就开始,不等待就足够了:有时它
ProcessClientAsync不会立即“离开”等待数据,而是执行一些“准备”工作,在此期间最好开始等待下一个连接。在这种情况下,您应该使用Task.Run:这两个选项都有效,主要是不应该有 operator
await。现在为什么编译器“发誓”。编译器抱怨是因为
await- 这仍然是一个错误;第二个问题例如通过日志来解决。第一个问题不是问题,但是编译器可以通过改变返回类型来“插入”: