美好的一天。我使用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;
}
WithCancellation
是我提前终止的扩展方法:
public static async Task<T> WithCancellation<T>(
this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(
s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
return await task;
}
手动阅读停止后,当我拨打Dispose
时,会发生System.ObjectDisposedException
。 CallStack
:
> 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
如果我理解正确,ReceiveAsync
中的错误根,在我的方法中就是准确的。但我不知道如何解决它。
我必须采取哪些措施来纠正此错误?
用户评论后更新:
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-а
}
catch(ObjectDisposedException) { }
return result.Buffer;
}
和Dispose
调用:
public void Dispose()
{
this.cancelRecieve?.Cancel();
this.cancelRecieve?.Dispose();
try
{
this.client?.Close();
}
catch(ObjectDisposedException) { }
}
但catch
对ObjectDisposedException
没有反应。
答案 0 :(得分:1)
因此,经过近一周的苦难,我找到了原因和解决方案。
首先,我查看了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);
}
第二次,我发现这篇帖子的答案很完美:How to abort socket's BeginReceive()?,其中说:
要取消对BeginConnect()方法的挂起调用,请关闭Socket。在异步操作正在进行时调用Close()方法时,将调用提供给BeginConnect()方法的回调。对EndConnect(IAsyncResult)方法的后续调用将抛出ObjectDisposedException以指示操作已被取消。
而且,正如我们所看到的,原始ReceiveAsync
方法会将ObjectDisposedException
返回给我们,因为在IOOperation
调用后Close
尚未完成。
为了克服这个问题,我这样做了:
新ReceiveAsync
实现:
/// <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);
新Dispose
实现:
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.cancelReceive?.Cancel();
this.client?.Close();
this.cancelReceive?.Dispose();
}
}
我非常希望,我的决定将剥夺其他人的痛苦。
答案 1 :(得分:0)
取消待处理接收的唯一方法是断开/停止/处置。这是对的。您需要捕获并忽略该异常。
.NET Framework的一个令人遗憾的设计问题是,这是唯一的方法。
请注意,WithCancellation
不会取消IO。接收仍在运行。这就是为什么WithCancellation
必须遵循处理套接字以确保没有其他待处理的IO的原因。