在异步函数中使用async?

时间:2014-06-26 16:45:59

标签: c# sockets networking asynchronous tcp

我正在创建一个简单的服务器来侦听客户端,这些客户端将读取客户端请求,进行一些计算,将响应发送回客户端并再次关闭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()?我很好奇,如果它甚至重要,因为我已经在一个不会阻止主线程的异步函数。

所以我最后的问题是:

  1. 这是完成此任务的最佳/正确方法(也是使用ManualResetEvent类)?
  2. 在这种情况下,在读取和写入流时使用异步或非异步操作会有什么不同吗? (因为我没有阻止主线程)
  3. 如果它滞后,发送/接收数据需要1-2秒,那么在异步和非同步操作之间进行选择仍然很重要吗?

  4. 更新新的改进

    由于答案,我已将代码更新为:

    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
        }
    }
    

2 个答案:

答案 0 :(得分:3)

马上我发现了两个常见的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.
});

答案 1 :(得分:2)

  

这是完成此任务的最佳/正确方法(也是使用ManualResetEvent类)吗?

没有。您启动异步操作,然后立即等待它。出于某种原因,我经常看到这种疯狂的舞蹈。让它同步:

while (true) {
 var clientSocket = Accept();
 ProcessClientAsync(clientSocket);
}

这么简单。

  

在这种情况下,在读取和写入流时使用异步或非异步操作会有什么不同吗?

如果您有很多客户端,那么在套接字上使用异步IO很有用。对于几十个,您可以只使用带线程的同步IO。 Async IO不会阻塞线程(每个线程使用1MB的堆栈空间)。

如果您决定使用异步IO,ProcessClientAsync应该是您现在拥有的异步功能。

如果您决定使用同步IO,请在新线程上启动ProcessClientAsync,以便能够同时处理多个客户端。

  

如果它滞后,发送/接收数据需要1-2秒,那么在异步和非同步操作之间进行选择仍然很重要吗?

只要您独立处理个人客户,您就可以了。同步和异步之间的选择只能在高规模发挥作用(同时打开多个连接)。

通过在不需要的情况下执行async来使事情过于复杂是一个常见的错误。基本上所有教程都会犯这个错误。