C# 取消使用异步函数创建的可观察对象

C# 取消使用异步函数创建的可观察对象,c#,async-await,task,.net-core,system.reactive,C#,Async Await,Task,.net Core,System.reactive,我是rx的新手,一直在使用点网中的反应式扩展编写一些网络代码。我的问题是,当我通过提供的令牌触发取消时,我用异步函数创建的tcpclient的可观察性并没有像我预期的那样完成。以下是我遇到问题的代码的简化版本: public static class ListenerExtensions { public static IObservable<TcpClient> ToListenerObservable( this IPEndPoint endpoint,

我是rx的新手,一直在使用点网中的反应式扩展编写一些网络代码。我的问题是,当我通过提供的令牌触发取消时,我用异步函数创建的tcpclient的可观察性并没有像我预期的那样完成。以下是我遇到问题的代码的简化版本:

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);
    }
}
公共静态类ListenerExtensions
{
公共静态IObservable to ListenerObservable(
此iEndpoint端点,
(待办事项)
{
返回新的TcpListener(endpoint).ToListenerObservable(backlog);
}
公共静态IObservable to ListenerObservable(
这个TcpListener听众,
(待办事项)
{
返回可观察的。创建(异步(观察者,令牌)=>
{
listener.Start(backlog);
尝试
{
而(!token.IsCancellationRequested)
OnNext(wait Task.Run(()=>listener.AcceptTcpClientAsync(),token));
//这不会打印,也不会调用onCompleted。
Console.WriteLine(“完成…”);
observer.OnCompleted();
}
捕获(System.Exception错误)
{
观察者:一个错误(错误);
}
最后
{
//这永远不会执行,我的程序会在不关闭侦听器的情况下退出。
Console.WriteLine(“停止侦听器…”);
listener.Stop();
}
});
}
}
班级计划
{
静态void Main(字符串[]参数)
{
var home=new-IPEndPoint(IPAddress.Parse(“127.0.0.1”),2323);
var cancellation=新的CancellationTokenSource();
主页。可观察到的ToListener(10)
.订阅(
onNext:c=>Console.WriteLine($“{c.Client.RemoteEndPoint}已连接”),
OneError:e=>Console.WriteLine($“错误:{e.Message}”),
onCompleted:()=>Console.WriteLine(“完成”),//从未发生
令牌:取消。令牌);
Console.WriteLine(“任何要取消的键”);
Console.ReadKey();
取消;
睡眠(1000);
}
}

如果我运行这个并连接到localhost:2323,我可以看到我得到了一系列已连接的tcpclient。但是,如果我触发取消cancellationtoken,程序将退出,而不会关闭侦听器并像我预期的那样发出onCompleted事件。我做错了什么?

所以我对这里的一些事情感到困惑。帮助我走上正轨。事实上,我最终从那里复制了代码

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;
        }
    }
}
公共静态类任务取消
{
带取消的公共静态异步任务(
这项任务,,
取消令牌(令牌)
{
var cancellation=new TaskCompletionSource();
使用(token.Register)(s=>
(作为TaskCompletionSource).TrySetResult(true),取消)
{
if(任务!=等待任务.wheny(任务,取消.任务))
抛出新的OperationCanceledException(令牌);
返回等待任务;
}
}
}
将其与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();
        }
    });
}
公共静态IObservable to ListenerObservable(
这个TcpListener听众,
(待办事项)
{
返回可观察的。创建(异步(观察者,令牌)=>
{
listener.Start(待办事项)
尝试
{
而(!token.IsCancellationRequested)
{
OnNext(wait listener.AcceptCpclientAsync()中)
.带取消(代币));
}
}
捕获(操作取消异常)
{
Console.WriteLine(“完成…”);
observer.OnCompleted();
}
捕获(System.Exception错误)
{
观察者:一个错误(错误);
}
最后
{
Console.WriteLine(“停止侦听器…”);
listener.Stop();
}
});
}

现在一切正常。

所以我对这里的一些事情感到困惑。帮助我走上正轨。事实上,我最终从那里复制了代码

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;
        }
    }
}
公共静态类任务取消
{
带取消的公共静态异步任务(
这项任务,,
取消令牌(令牌)
{
var cancellation=new TaskCompletionSource();
使用(token.Register)(s=>
(作为TaskCompletionSource).TrySetResult(true),取消)
{
if(任务!=等待任务.wheny(任务,取消.任务))
抛出新的OperationCanceledException(令牌);
返回等待任务;
}
}
}
将其与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();
        }
    });
}
公共静态IObservable to ListenerObservable(
这个TcpListener听众,
(待办事项)
{
返回可观察的。创建(异步(观察者,令牌)=>
{
listener.Start(待办事项)
尝试
{
而(!token.IsCancellationRequested)
{
OnNext(wait listener.AcceptCpclientAsync()中)
.带取消(代币));
}
}
捕获(操作取消异常)
{
Console.WriteLine(“完成…”);
observer.OnCompleted();
}
捕获(System.Exception错误)
{
观察者:一个错误(错误);
}
最后
{
Console.WriteLine(“停止侦听器…”);
listener.Stop();
}
});
}

现在一切正常。

尽量避免写太多的
尝试
/
捕获
代码,并用取消令牌混日子总是好的。这里有一种不用离开标准Rx运营商的方法。请不要这样,我无法完全测试这段代码,所以它可能仍然需要一些调整

试试这个:

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);
var查询=可观察