asynchronousnetworking操作从未完成

我有几个asynchronousnetworking操作返回一个可能永远不会完成的任务:

  1. UdpClient.ReceiveAsync不接受CancellationToken
  2. TcpClient.GetStream返回一个不尊重Stream.ReadAsync上的CancellationTokenNetworkStream (仅在操作开始时检查取消)

两者都等待可能永远不会到来的消息(因为丢包或没有响应)。 这意味着我有从未完成的幻影任务,永远不会运行的延续,并使用套接字保持。 我知道我可以使用TimeoutAfter ,但这只会解决延续问题。

那我该怎么办?

所以我在IDisposable上做了一个扩展方法,创build了一个CancellationToken ,它在超时时处理连接,所以任务结束,一切都继续:

 public static IDisposable CreateTimeoutScope(this IDisposable disposable, TimeSpan timeSpan) { var cancellationTokenSource = new CancellationTokenSource(timeSpan); var cancellationTokenRegistration = cancellationTokenSource.Token.Register(disposable.Dispose); return new DisposableScope( () => { cancellationTokenRegistration.Dispose(); cancellationTokenSource.Dispose(); disposable.Dispose(); }); } 

而且使用非常简单:

 try { var client = new UdpClient(); using (client.CreateTimeoutScope(TimeSpan.FromSeconds(2))) { var result = await client.ReceiveAsync(); // Handle result } } catch (ObjectDisposedException) { return null; } 

额外信息:

 public sealed class DisposableScope : IDisposable { private readonly Action _closeScopeAction; public DisposableScope(Action closeScopeAction) { _closeScopeAction = closeScopeAction; } public void Dispose() { _closeScopeAction(); } } 

那我该怎么办?

在这种特殊情况下,我宁愿使用UdpClient.Client.ReceiveTimeoutTcpClient.ReceiveTimeout来正常超时UDP或TCP接收操作。 我想要来自套接字的超时错误,而不是来自任何外部来源。

如果除此之外,我还需要观察一些其他的取消事件,比如点击一个UIbutton,我只会使用Stephen Toub的“如何取消不可取消的asynchronous操作?”中的 WithCancellation 。 , 喜欢这个:

 using (var client = new UdpClient()) { UdpClient.Client.ReceiveTimeout = 2000; var result = await client.ReceiveAsync().WithCancellation(userToken); // ... } 

为了解决这个问题 ,如果ReceiveTimeoutReceiveAsync没有影响,我仍然使用WithCancellation

 using (var client = new UdpClient()) using (var cts = CancellationTokenSource.CreateLinkedTokenSource(userToken)) { UdpClient.Client.ReceiveTimeout = 2000; cts.CancelAfter(2000); var result = await client.ReceiveAsync().WithCancellation(cts.Token); // ... } 

国际海事组织,这更清楚地表明我作为开发人员的意图,更容易被第三方阅读。 另外,我不需要捕捉ObjectDisposedExceptionexception。 我仍然需要在我的客户端代码中的某个地方观察OperationCanceledException ,但是我会这样做。 OperationCanceledException通常突出其他exception,我有一个选项来检查OperationCanceledException.CancellationToken观察取消的原因。

除此之外,与@ I3arnon的答案没有多大区别。 我只是不觉得我需要另一种模式,因为我已经有WithCancellation在我的处置。

为了进一步解决评论:

  • 我只会在客户端代码中捕获OperationCanceledException ,即:
 async void Button_Click(sender o, EventArgs args) { try { await DoSocketStuffAsync(_userCancellationToken.Token); } catch (Exception ex) { while (ex is AggregateException) ex = ex.InnerException; if (ex is OperationCanceledException) return; // ignore if cancelled // report otherwise MessageBox.Show(ex.Message); } } 
  • 是的,我将使用WithCancellation每个ReadAsync调用,我喜欢这个事实,出于以下原因。 首先,我可以创build一个扩展ReceiveAsyncWithToken
 public static class UdpClientExt { public static Task<UdpReceiveResult> ReceiveAsyncWithToken( this UdpClient client, CancellationToken token) { return client.ReceiveAsync().WithCancellation(token); } } 

其次,从现在开始的3年,我可能正在审查这个.NET 6.0的代码。 届时,微软可能会有一个新的API, UdpClient.ReceiveAsyncWithTimeout 。 在我的情况下,我只是用ReceiveAsyncWithTimeout(timeout, userToken)replaceReceiveAsyncWithToken(token)ReceiveAsync().WithCancellation(token) ReceiveAsyncWithTimeout(timeout, userToken) 。 处理CreateTimeoutScope并不是那么明显。