UdpClient.ReceiveAsync正确提前终止

时间:2016-12-07 14:28:04

标签: c# asynchronous udpclient objectdisposedexception

美好的一天。我使用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.ObjectDisposedExceptionCallStack

>   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) { }
}

catchObjectDisposedException没有反应。

2 个答案:

答案 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的原因。