C# UdpClient.ReceiveAsync正确提前终止
你好。我使用的是C# UdpClient.ReceiveAsync正确提前终止,c#,asynchronous,udpclient,objectdisposedexception,C#,Asynchronous,Udpclient,Objectdisposedexception,你好。我使用的是UdpClient,上面有包装器 对于阅读,我有异步方法: private async Task<byte[]> Receive(UdpClient client, CancellationToken breakToken) { // Выход из async, если произошёл CancellationRequest breakToken.ThrowIfCancellationRequested(); UdpReceiveR
UdpClient
,上面有包装器
对于阅读,我有异步方法:
private async Task<byte[]> Receive(UdpClient client, CancellationToken breakToken)
{
// Выход из async, если произошёл CancellationRequest
breakToken.ThrowIfCancellationRequested();
UdpReceiveResult result;
try
{
result = await client.ReceiveAsync().WithCancellation(breakToken);
}
catch(OperationCanceledException)
{
// Штатная ситуация ручной остановки Task-а
}
return result.Buffer;
}
在手动读取停止后,当我调用Dispose
时,会发生System.ObjectDisposedException
<代码>调用堆栈:
> System.dll!System.Net.Sockets.UdpClient.EndReceive(System.IAsyncResult asyncResult, ref System.Net.IPEndPoint remoteEP) Unknown
System.dll!System.Net.Sockets.UdpClient.ReceiveAsync.AnonymousMethod__64_1(System.IAsyncResult ar) Unknown
mscorlib.dll!System.Threading.Tasks.TaskFactory<System.Net.Sockets.UdpReceiveResult>.FromAsyncCoreLogic(System.IAsyncResult iar, System.Func<System.IAsyncResult, System.Net.Sockets.UdpReceiveResult> endFunction, System.Action<System.IAsyncResult> endAction, System.Threading.Tasks.Task<System.Net.Sockets.UdpReceiveResult> promise, bool requiresSynchronization) Unknown
mscorlib.dll!System.Threading.Tasks.TaskFactory<System.Net.Sockets.UdpReceiveResult>.FromAsyncImpl.AnonymousMethod__0(System.IAsyncResult iar) Unknown
System.dll!System.Net.LazyAsyncResult.Complete(System.IntPtr userToken) Unknown
System.dll!System.Net.ContextAwareResult.CompleteCallback(object state) Unknown
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Unknown
System.dll!System.Net.ContextAwareResult.Complete(System.IntPtr userToken) Unknown
System.dll!System.Net.LazyAsyncResult.ProtectedInvokeCallback(object result, System.IntPtr userToken) Unknown
System.dll!System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) Unknown
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) Unknown
和Dispose
调用:
public void Dispose()
{
this.cancelRecieve?.Cancel();
this.cancelRecieve?.Dispose();
try
{
this.client?.Close();
}
catch(ObjectDisposedException) { }
}
但是
catch
不会对ObjectDisposedException
做出反应。取消挂起接收的唯一方法是像您那样断开/停止/处置。这是正确的。您需要捕获并忽略该异常
对于.NET Framework来说,这是一个不幸的设计问题,因为这是实现它的唯一方法
请注意,带取消功能的
不会取消IO。接收仍在运行。这就是为什么with cancellation
之后必须处理套接字,以确保没有更多的未决IOs。因此,在经历了近一周的痛苦之后,我找到了原因和解决方案
首先,我查看了UdpClient
源代码。ReceiveAsync
方法:
[HostProtection(ExternalThreading = true)]
public Task<UdpReceiveResult> ReceiveAsync()
{
return Task<UdpReceiveResult>.Factory.FromAsync((callback, state) => BeginReceive(callback, state), (ar)=>
{
IPEndPoint remoteEP = null;
Byte[] buffer = EndReceive(ar, ref remoteEP);
return new UdpReceiveResult(buffer, remoteEP);
}, null);
}
新建Dispose
实现:
/// <summary>
/// Асинхронный запрос на ожидание приёма данных с возможностью досрочного выхода
/// (для выхода из ожидания вызовите метод Disconnect())
/// </summary>
/// <param name="client">Рабочий экземпляр класса UdpClient</param>
/// <param name="breakToken">Признак досрочного завершения</param>
/// <returns>Если breakToken произошёл до вызова данного метода или в режиме ожидания
/// ответа, вернёт пустой UdpReceiveResult; при удачном получении ответа-результат
/// асинхронной операции чтения</returns>
public Task<UdpReceiveResult> ReceiveAsync(UdpClient client, CancellationToken breakToken)
=> breakToken.IsCancellationRequested
? Task<UdpReceiveResult>.Run(() => new UdpReceiveResult())
: Task<UdpReceiveResult>.Factory.FromAsync(
(callback, state) => client.BeginReceive(callback, state),
(ar) =>
{
/// Предотвращение <exception cref="ObjectDisposedException"/>
if (breakToken.IsCancellationRequested)
return new UdpReceiveResult();
IPEndPoint remoteEP = null;
var buffer = client.EndReceive(ar, ref remoteEP);
return new UdpReceiveResult(buffer, remoteEP);
},
null);
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.cancelReceive?.Cancel();
this.client?.Close();
this.cancelReceive?.Dispose();
}
}
我非常希望,我的决定将剥夺其他人我所经历的痛苦。但如果我只调用Dispose
(删除。在接收中使用取消(breakToken)
),此对象将不会被处理。我听不懂ObjectDisposedException,我已经试过了…不知道你的意思。您拥有的代码很好(假设在超时的情况下,您也会终止套接字)。但是,您需要吞下ObjectDisposedException.try{result=await client.ReceiveAsync().WithCancellation(breakToken)}catch(OperationCanceledException){//并尝试{this.client?.Close();}catch(ObjectDisposedException){}未捕获此异常。是否向breakToken发送信号?在这种情况下,接收任务被丢弃。接收任务是OBJEDSPEX出现故障的任务。是的,但忘了在此处触发它)这在所有需要的情况下都无法调用EndReceive。如果调用EndReceive,您将再次收到ObjectDisposedException。但我已经解释了如何处理这个例外。您的原始代码很好,除了坏掉的异常。文档中说必须调用它。不调用它是一个使用错误。实际上,它可能会泄漏内存。捕获一个异常来处理它是完全正确的。在这里什么都不做是合适的,这不是保证。此外,它似乎并没有表明没有泄漏。而且,没有理由这样做。新的ReceiveAsync代码更加复杂。您所需要的只是catch(ODE),框架是正确的。根据文档,您误用了它。关闭是正确的,但必须始终调用End方法。答案是错误的,原因与我已经解释过的相同:不能保证是这样。答案取决于对当前代码的反编译。随时都可能中断。文档状态异步BeginReceive操作必须通过调用EndReceive方法来完成。
/// <summary>
/// Асинхронный запрос на ожидание приёма данных с возможностью досрочного выхода
/// (для выхода из ожидания вызовите метод Disconnect())
/// </summary>
/// <param name="client">Рабочий экземпляр класса UdpClient</param>
/// <param name="breakToken">Признак досрочного завершения</param>
/// <returns>Если breakToken произошёл до вызова данного метода или в режиме ожидания
/// ответа, вернёт пустой UdpReceiveResult; при удачном получении ответа-результат
/// асинхронной операции чтения</returns>
public Task<UdpReceiveResult> ReceiveAsync(UdpClient client, CancellationToken breakToken)
=> breakToken.IsCancellationRequested
? Task<UdpReceiveResult>.Run(() => new UdpReceiveResult())
: Task<UdpReceiveResult>.Factory.FromAsync(
(callback, state) => client.BeginReceive(callback, state),
(ar) =>
{
/// Предотвращение <exception cref="ObjectDisposedException"/>
if (breakToken.IsCancellationRequested)
return new UdpReceiveResult();
IPEndPoint remoteEP = null;
var buffer = client.EndReceive(ar, ref remoteEP);
return new UdpReceiveResult(buffer, remoteEP);
},
null);
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.cancelReceive?.Cancel();
this.client?.Close();
this.cancelReceive?.Dispose();
}
}