Dot-Net

帶有取消令牌的 NetworkStream.ReadAsync 永遠不會取消

  • September 14, 2012

這裡證明。

知道這段程式碼有什麼問題嗎?

   [TestMethod]
   public void TestTest()
   {
       var tcp = new TcpClient() { ReceiveTimeout = 5000, SendTimeout = 20000 };
       tcp.Connect(IPAddress.Parse("176.31.100.115"), 25);
       bool ok = Read(tcp.GetStream()).Wait(30000);
       Assert.IsTrue(ok);
   }

   async Task Read(NetworkStream stream)
   {
       using (var cancellationTokenSource = new CancellationTokenSource(5000))
       {
           int receivedCount;
           try
           {
               var buffer = new byte[1000];
               receivedCount = await stream.ReadAsync(buffer, 0, 1000, cancellationTokenSource.Token);
           }
           catch (TimeoutException e)
           {
               receivedCount = -1;
           }
       }
   }

我終於找到了解決方法。使用 Task.WaitAny 將非同步呼叫與延遲任務 (Task.Delay) 相結合。當延遲在 io 任務之前過去時,關閉流。這將強制任務停止。您應該正確處理 io 任務的非同步異常。並且您應該為延遲任務和 io 任務添加一個延續任務。

它也適用於 tcp 連接。在另一個執行緒中關閉連接(您可以認為它是延遲任務執行緒)會強制使用/等待此連接停止的所有非同步任務。

  • 編輯 -

@vtortola 建議的另一個更清潔的解決方案:使用取消令牌註冊對流的呼叫。關閉:

async ValueTask Read(NetworkStream stream, TimeSpan timeout = default)
{
   if(timeout == default(TimeSpan))
     timeout = TimeSpan.FromSeconds(5);

   using var cts = new CancellationTokenSource(timeout); //C# 8 syntax
   using(cts.Token.Register(() => stream.Close()))
   {
      int receivedCount;
      try
      {
          var buffer = new byte[30000];
          receivedCount = await stream.ReadAsync(buffer, 0, 30000, tcs.Token).ConfigureAwait(false);
      }
      catch (TimeoutException)
      {
          receivedCount = -1;
      }
   }
}

引用自:https://stackoverflow.com/questions/12421989