在async函数中使用async
本文关键字:async 函数 | 更新日期: 2023-09-27 18:10:28
我正在制作一个简单的服务器,侦听客户端,它将读取客户端请求,做一些计算,发送响应回客户端并再次关闭ASAP(有点类似于HTTP)。
每秒钟可能有很多连接,所以我希望它尽可能地快速和高效。
到目前为止,我能想到的最好的方法如下面的例子所示:private static ManualResetEvent gate = new ManualResetEvent(false);
static async void ListenToClient(TcpListener listener)
{
Console.WriteLine("Waiting for connection");
TcpClient client = await listener.AcceptTcpClientAsync();
Console.WriteLine("Connection accepted & establised");
gate.Set(); //Unblocks the mainthread
Stream stream = client.GetStream();
byte[] requestBuffer = new byte[1024];
int size = await stream.ReadAsync(requestBuffer, 0, requestBuffer.Length);
//PSEUDO CODE: Do some calculations
byte[] responseBuffer = Encoding.ASCII.GetBytes("Ok");
await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length);
stream.Close();
client.Close();
}
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();
while (true)
{
gate.Reset();
ListenToClient(listener);
gate.WaitOne(); //Blocks the main thread and waits until the gate.Set() is called
}
}
注意:为了简单起见,我没有做任何类似try-catch的错误处理,我知道这里的响应总是"Ok"
这里的代码只是等待连接,当它到达await listener.AcceptTcpClientAsync()
时,然后它跳转回while循环并等待连接建立并调用gate.Set(),以便它可以再次侦听新连接。因此,这将允许多个客户端同时(特别是如果计算可能需要很长时间)
但是我应该使用stream.ReadAsync()还是stream.Read()来代替吗?我很好奇这是否重要,因为我已经在一个异步函数中,它不会阻塞主线程。
我的最后一个问题是:
- 这是最好的/正确的方式来完成这个任务(也通过使用ManualResetEvent类)?
- 在这种情况下,读写流时使用异步或非异步操作有什么区别吗?(因为我没有阻塞主线程)
- 如果它滞后,并且需要1-2秒来发送/接收数据,在异步和非异步操作之间选择仍然重要吗?
更新为新的改进
由于一个答案,我已经更新了我的代码如下:
private static ManualResetEvent gate = new ManualResetEvent(false);
static async Task ListenToClient(TcpListener listener)
{
//Same Code
}
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();
while (true)
{
gate.Reset();
Task task = ListenToClient(listener);
task.ContinueWith((Task paramTask) =>
{
//Inspect the paramTask
});
gate.WaitOne(); //Blocks the main thread and waits until the gate.Set() is called
}
}
马上我就看到了两个常见的async
错误:
async void
不要这样做。编译器甚至支持 async void
的唯一原因是为了处理现有的事件驱动接口。这不是其中之一,所以这是一个反模式。async void
有效地导致失去对该任务的任何响应方式或与之相关的任何操作,例如处理错误。
说到响应任务…
ListenToClient(listener);
你正在生成一个任务,但从未检查它的状态。如果任务中出现异常,你会怎么做?不管怎样,它不会被发现,它只会被默默地忽略。至少应该在任务完成后为其提供一个顶级回调。甚至像这样简单的事情:
ListenToClient(listener).ContinueWith(t =>
{
// t is the task. Examine it for errors, cancelations, etc.
// Respond to error conditions here.
});
这是最好的/正确的方式来完成这个任务(也通过使用ManualResetEvent类)?
。您启动一个异步操作,然后立即等待它。不知道为什么,我经常看到这种疯狂的舞蹈。只要让它同步:
while (true) {
var clientSocket = Accept();
ProcessClientAsync(clientSocket);
}
那么简单。
在这种情况下,读写流时使用异步或非异步操作有什么区别吗?
如果你有很多客户端,使用套接字异步IO是很好的。对于几十个线程,你可以只使用线程同步IO。异步IO是关于不阻塞线程(每个线程使用1MB的堆栈空间)。
如果你决定使用异步IO, ProcessClientAsync
应该是一个async函数,就像你现在有它一样。
如果你决定同步IO,在一个新线程上启动ProcessClientAsync
,以便能够同时处理多个客户端。
如果它滞后,并且需要1-2秒来发送/接收数据,在异步和非异步操作之间选择仍然重要吗?
只要独立处理单个客户机,就没有问题。同步和异步之间的选择只在大规模(同时打开数十个以上的连接)时起作用。
一个常见的错误是在不需要异步的情况下使事情过于复杂。基本上所有教程都会犯这个错误。