asynchronousnetworking操作从未完成
我有几个asynchronousnetworking操作返回一个可能永远不会完成的任务:
-
UdpClient.ReceiveAsync
不接受CancellationToken
-
TcpClient.GetStream
返回一个不尊重Stream.ReadAsync
上的CancellationToken
的NetworkStream
(仅在操作开始时检查取消)
两者都等待可能永远不会到来的消息(因为丢包或没有响应)。 这意味着我有从未完成的幻影任务,永远不会运行的延续,并使用套接字保持。 我知道我可以使用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.ReceiveTimeout
和TcpClient.ReceiveTimeout
来正常超时UDP或TCP接收操作。 我想要来自套接字的超时错误,而不是来自任何外部来源。
如果除此之外,我还需要观察一些其他的取消事件,比如点击一个UIbutton,我只会使用Stephen Toub的“如何取消不可取消的asynchronous操作?”中的 WithCancellation
。 , 喜欢这个:
using (var client = new UdpClient()) { UdpClient.Client.ReceiveTimeout = 2000; var result = await client.ReceiveAsync().WithCancellation(userToken); // ... }
为了解决这个问题 ,如果ReceiveTimeout
对ReceiveAsync
没有影响,我仍然使用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); // ... }
国际海事组织,这更清楚地表明我作为开发人员的意图,更容易被第三方阅读。 另外,我不需要捕捉ObjectDisposedException
exception。 我仍然需要在我的客户端代码中的某个地方观察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
并不是那么明显。