Observable.Using {WebSocket.ReceiveAsync();早点中止并取消

时间:2016-05-22 13:22:49

标签: c# .net websocket async-await system.reactive

在下面的代码中,Connect()似乎工作正常。但是webSocket.ReceiveAsync()中的ReceiveMessage()似乎在服务器没有提供任何内容的时候设置cancelToken.IsCancellationRequested = truewebSocket.State = Aborted。与此同时,相同的ReceiveAsync()调用会引发一个(无用的)OperationCanceledException,没有内部异常。

我使用了我认为是相同的WebSocket交互而没有这个问题的同一服务器,但使用更简单的异步代码。直到我尝试将它转换成一个可观察到我有这个问题。

它被Rx取消了,还是取消了?我发现在.NET WebSockets中通过未观察到的异常(http://www.salmanq.com/blog/5-things-you-probably-didnt-know-about-net-websockets/2013/04/)引起了这样的事情,但在添加TaskScheduler.UnobservedTaskException += ExceptionLogger之后我找不到任何证据。

有什么想法吗?

public static IObservable<string> GetWebSocketStream(Uri uri)
{
    var buffer = new byte[2048];
    return Observable
        .Using(cancelToken => Connect(uri, cancelToken),
            (webSocket, cancelToken) => Task.FromResult(
                Observable.Defer(
                    () => ReceiveMessage(webSocket, buffer, cancelToken).ToObservable()
                ).Repeat()
            )
        ).Publish().RefCount();
}

private static async Task<ClientWebSocket> Connect(Uri uri, CancellationToken token)
{
    var webSocket = new ClientWebSocket();
    await webSocket.ConnectAsync(uri, token).ConfigureAwait(false);
    return webSocket;
}

private static async Task<string> ReceiveMessage(ClientWebSocket webSocket, byte[] buffer, CancellationToken cancelToken)
{
    var segment = new ArraySegment<byte>(buffer);
    var message = new StringBuilder();
    WebSocketReceiveResult result;
    do
    {
        result = await webSocket.ReceiveAsync(segment, cancelToken).ConfigureAwait(false);
        message.Append(Encoding.UTF8.GetString(segment.Array, 0, result.Count));
    } while (!cancelToken.IsCancellationRequested && !result.EndOfMessage);
    return message.ToString();
}

编辑:为了澄清,我的问题/问题可能与BOTH WebSockets和Rx有关。以下类似的代码在同一设备上保持打开状态并接收数据:

public static void Test() {
    var ts = new CancellationTokenSource();
    AsyncTest(new Uri("ws://server/test/"), ts.Token).Wait();
}

public static async Task AsyncTest(Uri uri, CancellationToken token) {
    var socket = await GetConnectedWebSocket(uri, token);
    var buffer = new byte[4000];
    while (!token.IsCancellationRequested)
    {
        try
        {
            var msg = await ReceiveMessage(socket, buffer, token);
            Debug.WriteLine(msg);
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
            throw;
        }
    }
}

1 个答案:

答案 0 :(得分:0)

The problem is with cancelToken passed to ReceiveMessage function. In Rx implementation before Task finish (in your case Task.FromResult) cleanup code is executed and as a port if it cancelToken is disposed. I had the same problem when I tried to use such code:

return Observable.Using(cancelToken => Connect(uri, authToken, cancelToken), (webSocket, token) =>
            Task.FromResult(Observable.Create<string>((observer) => WebSocketReceiver.WebSocketReceiverSubscribe(observer, webSocket, cancelToken))))
            .Publish().RefCount();

but this code works:

return Observable.Using(cancelToken => Connect(uri, authToken, cancelToken), (webSocket, token) =>
            Task.FromResult(Observable.Create<string>((observer, ct) => WebSocketReceiver.WebSocketReceiverSubscribe(observer, webSocket, ct))))
            .Publish().RefCount();

As I can see Observable.Defer doesn't pass CancellationToken object so you will have to create external one and pass it to ReceiveMessage. Alternatively you can play with Observable.Create as in my example.