2013-11-21 3 views
4

다음과 같은 간단한 예를 고려하여 NetworkStream.ReadAsync 취소 (LinqPad에 출시 할 준비를 상승 계정 필요) :이 TcpListener

void Main() 
{ 
    Go(); 
    Thread.Sleep(100000); 
} 
async void Go() 
{ 
    TcpListener listener = new TcpListener(IPAddress.Any, 6666); 
    try 
    { 
     cts.Token.Register(() => Console.WriteLine("Token was canceled")); 
     listener.Start(); 
     using(TcpClient client = await listener.AcceptTcpClientAsync() 
               .ConfigureAwait(false)) 
     using(var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5))) 
     { 
      var stream=client.GetStream(); 
      var buffer=new byte[64]; 
      try 
      { 
       var amtRead = await stream.ReadAsync(buffer, 
                0, 
                buffer.Length, 
                cts.Token); 
       Console.WriteLine("finished"); 
      } 
      catch(TaskCanceledException) 
      { 
       Console.WriteLine("boom"); 
      } 
     } 
    } 
    finally 
    { 
     listener.Stop(); 
    } 
} 

내가 localhost:6666에 텔넷 클라이언트를 연결하고 5 초 동안 아무것도하지 않고 앉아서하는 경우, 왜 "토큰은 취소"되었지만 결코 "붐"(또는 "완료")을 보지 못합니까?

이 NetworkStream은 취소를 고려하지 않습니까?

Task.Delay()Task.WhenAny의 조합으로이 문제를 해결할 수 있지만 예상대로 작동하도록하는 것이 좋습니다. 반대로

취소 다음의 예 :

async void Go(CancellationToken ct) 
{ 
    using(var cts=new CancellationTokenSource(TimeSpan.FromSeconds(5))) 
    { 
     try 
     { 
      await Task.Delay(TimeSpan.FromSeconds(10),cts.Token) 
             .ConfigureAwait(false); 
     } 
     catch(TaskCanceledException) 
     { 
      Console.WriteLine("boom"); 
     } 
    } 
} 

인쇄 "붐"예상. 무슨 일이야?

답변

13

아니요, NetworkStream은 취소를 지원하지 않습니다.

불행히도 기본 Win32 API는 작업 당 취소를 항상 지원하지는 않습니다. 전통적으로 취소 할 수 있습니다 모두 특정 핸들에 대한 I/O는 the method to cancel a single I/O operation입니다. 대부분의 .NET BCL은 CancelIoEx을 포함하지 않은 XP API (또는 그 이전 버전)에 대해 작성되었습니다.

Stream 구현이이를 지원하지 않는 경우에도 취소 (및 비동기 I/O)를 "위장"하여이 문제를 복합화합니다. "가짜"지원 취소는 토큰을 즉시 확인한 다음 취소 할 수없는 일반 비동기 읽기를 시작합니다. 그게 바로 NetworkStream입니다.

소켓 (대부분의 Win32 유형)에서는 통신을 중단하려는 경우 핸들을 닫는 것이 일반적인 방법입니다. 이로 인해 모든 현재 작업 (읽기 및 쓰기 모두)이 실패합니다. 기술적으로 이는 BCL 스레드 안전성에 대한 문서화 된 위반이지만 작동합니다.

cts.Token.Register(() => client.Close()); 
... 
catch (ObjectDisposedException) 

다른 한편으로는, 당신이 (당신의 측면 읽고 그러나 다른면이 연결이 끊어졌습니다) 반 개방 시나리오를 감지하려는 경우

은 다음 가장 좋은 방법은 주기적으로 데이터를 전송하는 것입니다. 나는 이것을 더 많이 설명한다 on my blog.