【问题标题】:Unobserved task exception under stress during WebSocket SendAsyncWebSocket SendAsync期间压力下未观察到的任务异常
【发布时间】:2018-03-15 04:42:21
【问题描述】:

我正在强调我正在编写的服务,它使用取自 AcceptWebSocketAsyncWebSocket。我用来通过 WebSocket 发送消息的代码是这样的:

    static bool
    SendMessage(WebSocket webSocket, WebSocketMessage message, byte[] buffer, CancellationToken cancellationToken)
    {
        try {
            var endOfMessage = false;
            do {
                using(var timeout = new CancellationTokenSource(webSocketsTimeout))
                using(var lcts    = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeout.Token)) {
                    var count    = message.Content.Read(buffer, 0, buffer.Length);
                    endOfMessage = count < buffer.Length;
                    // ReSharper disable once MethodSupportsCancellation
                    webSocket
                        .SendAsync(new ArraySegment<byte>(buffer, 0, count), message.Type, endOfMessage, lcts.Token)
                        .Wait() // SendAsync should be canceled using the Token.
                    ;
                }
            } while(endOfMessage == false);

            return true;
        }
        catch(Exception e) {
            TraceConnectionError(e);
            return false;
        }
        finally {
            message.Dispose();
        }
    }

我的问题是在“压力”下(我每 30 秒打开和关闭 6 个连接,直到系统出现故障),我得到:

  Unhandled Exception: System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.Net.HttpListenerException: An operation was attempted on a nonexistent network connection
     at System.Net.WebSockets.WebSocketHttpListenerDuplexStream.WriteAsyncFast(HttpListenerAsyncEventArgs eventArgs)
     at System.Net.WebSockets.WebSocketHttpListenerDuplexStream.<MultipleWriteAsyncCore>d__38.MoveNext()
  --- End of stack trace from previous location where exception was thrown ---
     at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
     at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
     at System.Net.WebSockets.WebSocketBase.<SendFrameAsync>d__48.MoveNext()
  --- End of stack trace from previous location where exception was thrown ---
     at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
     at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
     at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
     at System.Net.WebSockets.WebSocketBase.WebSocketOperation.<Process>d__19.MoveNext()
  --- End of stack trace from previous location where exception was thrown ---
     at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
     at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
     at System.Net.WebSockets.WebSocketBase.<SendAsyncCore>d__47.MoveNext()
     --- End of inner exception stack trace ---
     at System.Threading.Tasks.TaskExceptionHolder.Finalize()

我使用的Wait() 不应该足以“观察”任务异常吗?

【问题讨论】:

  • 没有。 “using”语句有自己的异常处理程序。删除 using 语句。
  • 我不明白你的意思。 using 没有处理任何事情,异常在 SendAsync() 上引发,并且应该在 Wait() 上“观察到”...如果我删除 using 我只会得到泄漏(至少在 LinkedTokenSource 上) ...我错过了什么?
  • using 语句的内部是一个内置的异常处理程序。
  • 我理解 using 翻译成什么(基本上是 try..finally 与最后的处置)。该翻译如何解释我所看到的行为?
  • 异常表示:在不存在的网络连接上尝试了操作。可能是之前的“使用”超时,您没有测试连接是否关闭或为空。

标签: c# exception websocket task cancellation


【解决方案1】:

问题是 .NET 框架代码中的竞争条件。

我已报告错误here

作为一种解决方法,我保留了一个使用过的 WebSocket 列表,我会定期检查 State != Open,然后调用此代码:

public static class WebSocketXs
{
    readonly static Assembly  assembly                    = typeof(WebSocket).Assembly;
    readonly static FieldInfo m_InnerStream               = assembly.GetType("System.Net.WebSockets.WebSocketBase").GetField(nameof(m_InnerStream), BindingFlags.NonPublic | BindingFlags.Instance);
    readonly static FieldInfo m_ReadTaskCompletionSource  = assembly.GetType("System.Net.WebSockets.WebSocketHttpListenerDuplexStream").GetField(nameof(m_ReadTaskCompletionSource),  BindingFlags.NonPublic | BindingFlags.Instance);
    readonly static FieldInfo m_WriteTaskCompletionSource = assembly.GetType("System.Net.WebSockets.WebSocketHttpListenerDuplexStream").GetField(nameof(m_WriteTaskCompletionSource), BindingFlags.NonPublic | BindingFlags.Instance);
    readonly static FieldInfo[] completionSourceFields    = {m_ReadTaskCompletionSource, m_WriteTaskCompletionSource };

    /// <summary>
    /// This fixes a race that happens when a <see cref="WebSocket"/> fails and aborts after failure.
    /// The <see cref="completionSourceFields"/> have an Exception that is not observed as the <see cref="WebSocket.Abort()"/>
    /// done to WebSocketBase <see cref="m_InnerStream"/> is just <see cref="TaskCompletionSource{TResult}.TrySetCanceled()"/> which
    /// does nothing with the completion source <see cref="Task.Exception"/>.
    /// That in turn raises a <see cref="TaskScheduler.UnobservedTaskException"/>.
    /// </summary>
    public static void
    CleanUpAndDispose(this WebSocket ws)
    {
        foreach(var completionSourceField in completionSourceFields) {
            m_InnerStream
                .GetValue(ws)
                .Maybe(completionSourceField.GetValue)
                .Maybe(s => s as TaskCompletionSource<object>)?
                .Task
                .Exception
                .Maybe(_ => {}) // We just need to observe any exception.
            ;
        }
        ws.Dispose();
    }
}

【讨论】:

    猜你喜欢
    • 2016-11-25
    • 2013-11-26
    • 1970-01-01
    • 2011-12-14
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    • 2012-02-29
    • 2017-07-27
    相关资源
    最近更新 更多