簡體   English   中英

在異步函數中使用async?

[英]Using async in async function?

我正在制作一個簡單的服務器來監聽客戶端,這些客戶端將讀取客戶端請求,進行一些計算,將響應發送回客戶端並再次關閉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秒,那么在異步和非同步操作之間進行選擇仍然很重要嗎?

更新以獲得新的改進

由於答案,我已將我的代碼更新為:

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。 Async IO不會阻塞線程(每個線程使用1MB的堆棧空間)。

如果您決定使用異步IO,則ProcessClientAsync應該是您現在擁有的異步函數。

如果您決定使用同步IO,請在新線程上啟動ProcessClientAsync ,以便能夠同時處理多個客戶端。

如果它滯后,並且發送/接收數據需要1-2秒,那么在異步和非同步操作之間進行選擇仍然很重要嗎?

只要您獨立處理個人客戶,您就可以了。 同步和異步之間的選擇僅在高規模發揮作用(同時打開多個連接)。

通過在不需要的情況下進行異步來使事情過度復雜化是一個常見的錯誤。 基本上所有教程都會犯這個錯誤。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM