简体   繁体   English

取消使用异步函数创建的observable

[英]Canceling observable created with async function

I'm new to rx and have been working on some networking code using reactive extensions in dot net. 我是rx的新手,并且一直在使用dot net中的反应式扩展来处理一些网络代码。 My problem is that the observable of tcpClients i create with an async function is not completed as I expect when I trigger cancellation through a token I supply. 我的问题是,当我通过我提供的令牌触发取消时,我用异步函数创建的tcpClients的可观察性没有完成。 Here is a simplified version of the code I have problem with: 这是我遇到问题的代码的简化版本:

public static class ListenerExtensions
{
    public static IObservable<TcpClient> ToListenerObservable(
        this IPEndPoint endpoint,
        int backlog)
    {
        return new TcpListener(endpoint).ToListenerObservable(backlog);
    }

    public static IObservable<TcpClient> ToListenerObservable(
        this TcpListener listener,
        int backlog)
    {
        return Observable.Create<TcpClient>(async (observer, token) => 
        {
            listener.Start(backlog);

            try
            {
                while (!token.IsCancellationRequested)
                    observer.OnNext(await Task.Run(() => listener.AcceptTcpClientAsync(), token));
                //This never prints and onCompleted is never called.
                Console.WriteLine("Completing..");
                observer.OnCompleted();
            }
            catch (System.Exception error)
            {
                observer.OnError(error);   
            }
            finally
            {
                //This is never executed and my progam exits without closing the listener.
                Console.WriteLine("Stopping listener...");
                listener.Stop();
            }
        });
    }
}
class Program
{
   static void Main(string[] args)
    {
        var home = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2323);
        var cancellation = new CancellationTokenSource();

        home.ToListenerObservable(10)
            .Subscribe(
                onNext: c => Console.WriteLine($"{c.Client.RemoteEndPoint} connected"),
                onError: e => Console.WriteLine($"Error: {e.Message}"),
                onCompleted: () => Console.WriteLine("Complete"), // Never happens
                token: cancellation.Token);

        Console.WriteLine("Any key to cancel");
        Console.ReadKey();
        cancellation.Cancel();
        Thread.Sleep(1000);
    }
}

If I run this and connect to localhost:2323 I can see that I get a sequence of connected tcpClients. 如果我运行它并连接到localhost:2323我可以看到我得到一系列连接的tcpClients。 If I however trigger the cancellation of the cancellationtoken the program exits without closing the listener and emitting the onCompleted event like I expect. 但是,如果我触发取消取消,则程序退出而不关闭监听器并像我期望的那样发出onCompleted事件。 What am I doing wrong? 我究竟做错了什么?

It's always good to try to avoid writing too much try / catch code and to muck around with cancellation tokens. 尝试避免编写过多的try / catch代码并使用取消令牌来解决问题总是好的。 Here's a way of doing what you're doing without moving away from standard Rx operators. 这是一种在不偏离标准Rx运算符的情况下完成您正在做的事情的方法。 Please not I couldn't fully test this code so it might still require a little tweaking. 请不要我无法完全测试此代码,因此可能仍需要稍微调整一下。

Try this: 尝试这个:

var query = Observable.Create<TcpClient>(o =>
{
    var home = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2323);
    var listener = new TcpListener(home);
    listener.Start();
    return
        Observable
            .Defer(() => Observable.FromAsync(() => listener.AcceptTcpClientAsync()))
            .Repeat()
            .Subscribe(o);
});

var completer = new Subject<Unit>();
var subscription =
    query
        .TakeUntil(completer)
        .Subscribe(
            onNext: c => Console.WriteLine($"{c.Client.RemoteEndPoint} connected"),
            onError: e => Console.WriteLine($"Error: {e.Message}"),
            onCompleted: () => Console.WriteLine("Complete"));

Console.WriteLine("Enter to cancel");
Console.ReadLine();
completer.OnNext(Unit.Default);
Thread.Sleep(1000);

The key thing here is the completer that asks like a cancellation token. 这里的关键是completer要求取消令牌。 It finishes the subscription naturally, unlike subscription.Dispose() which finishes it without the OnCompleted call. 它自然地完成了订阅,不像subscription.Dispose() ,它在没有OnCompleted调用的情况下完成它。

So turns out I was confused about a few things here. 事实证明我对这里的一些事感到困惑。 This article helped me get on track. 这篇文章帮助我走上正轨。 In fact I ended up copying the code from there. 事实上,我最终从那里复制代码。

public static class TaskCancellations
{
    public static async Task<T> WithCancellation<T>(
        this Task<T> task,
        CancellationToken token)
    {
        var cancellation = new TaskCompletionSource<bool>();
        using (token.Register(s =>
             (s as TaskCompletionSource<bool>).TrySetResult(true), cancellation))
        {
            if (task != await Task.WhenAny(task, cancellation.Task))
                throw new OperationCanceledException(token);
            return await task;
        }
    }
}

And use it with the tcplistener like this: 并将它与tcplistener一起使用,如下所示:

public static IObservable<TcpClient> ToListenerObservable(
    this TcpListener listener,
    int backlog)
{
    return Observable.Create<TcpClient>(async (observer, token) =>
    {
        listener.Start(backlog)
        try
        {
            while (!token.IsCancellationRequested)
            {
                observer.OnNext(await listener.AcceptTcpClientAsync()
                    .WithCancellation(token));
            }
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Completing...");
            observer.OnCompleted();
        }
        catch (System.Exception error)
        {
            observer.OnError(error);
        }
        finally
        {
            Console.WriteLine("Stopping listener...");
            listener.Stop();
        }
    });
}

Everything works as expected now. 现在一切都按预期工作了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM