我是rx的新手,并且一直在使用dot net中的反应式扩展来处理一些网络代码。我的问题是,当我通过我提供的令牌触发取消时,我用异步函数创建的tcpClients的可观察性没有完成。这是我遇到问题的代码的简化版本:
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);
}
}
如果我运行它并连接到localhost:2323我可以看到我得到一系列连接的tcpClients。但是,如果我触发取消取消,则程序退出而不关闭监听器并像我期望的那样发出onCompleted事件。我做错了什么?
答案 0 :(得分:1)
事实证明我对这里的一些事感到困惑。 This article帮助我走上正轨。事实上,我最终从那里复制了代码。
no such file or directory
并将其与tcplistener一起使用,如下所示:
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;
}
}
}
现在一切正常。
答案 1 :(得分:1)
尽量避免编写过多的try
/ catch
代码并使用取消令牌进行处理,这总是好的。这是一种在不偏离标准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);
这里的关键是completer
,它要求取消令牌。它会自然地完成订阅,而不像subscription.Dispose()
那样在没有OnCompleted
调用的情况下完成订阅。