C# Rx.NET以什么方式生成可取消的可观察文件名?
我想生成一个可观察的文件,以便随时可以取消对文件名的发现。在本例中,取消将在1秒内自动进行 这是我目前的代码:C# Rx.NET以什么方式生成可取消的可观察文件名?,c#,system.reactive,C#,System.reactive,我想生成一个可观察的文件,以便随时可以取消对文件名的发现。在本例中,取消将在1秒内自动进行 这是我目前的代码: class Program { static void Main() { try { RunAsync(@"\\abc\xyz").GetAwaiter().GetResult(); } catch (Exception exc) { Cons
class Program
{
static void Main()
{
try
{
RunAsync(@"\\abc\xyz").GetAwaiter().GetResult();
}
catch (Exception exc)
{
Console.Error.WriteLine(exc);
}
Console.Write("Press Enter to exit");
Console.ReadLine();
}
private static async Task RunAsync(string path)
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
await GetFileSource(path, cts);
}
private static IObservable<string> GetFileSource(string path, CancellationTokenSource cts)
{
return Observable.Create<string>(obs => Task.Run(async () =>
{
Console.WriteLine("Inside Before");
foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Take(50))
{
cts.Token.ThrowIfCancellationRequested();
obs.OnNext(file);
await Task.Delay(100);
}
Console.WriteLine("Inside After");
obs.OnCompleted();
return Disposable.Empty;
}, cts.Token))
.Do(Console.WriteLine);
}
}
如果还有更多,我不喜欢我的实现的两个方面-请随时指出:
我有一个可枚举的文件,但我手动迭代每个文件。我可以使用ToObservable扩展吗?
我不知道如何使用传递给Task.Run的cts.Token。必须使用从外部上下文GetFileSource参数捕获的cts。我觉得很难看。
应该这样做吗?必须是更好的方法。我建议您避免使用可观察的。请在可以使用其他运算符时创建 还有,当你做一次性退货时。空;在可观察范围内。创建您正在创建的可观察范围无法被正常Rx订阅停止。这可能导致内存泄漏和不必要的处理 最后,抛出异常以结束正常计算是一个坏主意 有一种很好的清洁解决方案似乎可以满足您的需求:
private static IObservable<string> GetFileSource(string path, CancellationTokenSource cts)
{
return
Directory
.EnumerateFiles(path, "*", SearchOption.AllDirectories)
.ToObservable()
.Take(50)
.TakeWhile(f => !cts.IsCancellationRequested);
}
我唯一没有包括的是任务。Delay100;。你为什么要这样做?我仍然不相信这真的是一个反应性问题,你要求对制片人施加反压力,这实际上与反应性的工作方式背道而驰 这就是说,如果您打算这样做,您应该意识到非常细粒度的时间操作几乎总是应该委托给调度程序,而不是尝试与任务和取消令牌进行协调。因此,我将重构为如下所示:
public static IObservable<string> GetFileSource(string path, Func<string, Task<string>> processor, IScheduler scheduler = null) {
scheduler = scheduler ?? Scheduler.Default;
return Observable.Create<string>(obs =>
{
//Grab the enumerator as our iteration state.
var enumerator = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
.GetEnumerator();
return scheduler.Schedule(enumerator, async (e, recurse) =>
{
if (!e.MoveNext())
{
obs.OnCompleted();
return;
}
//Wait here until processing is done before moving on
obs.OnNext(await processor(e.Current));
//Recursively schedule
recurse(e);
});
});
}
您还可以看到一个更高级的实现示例。这似乎不是一个非常被动的问题,因为您实际上只是在枚举集合。是什么原因导致取消?你有没有看过Parallel.ForEach或PLinq,它们也支持中间迭代取消?这是一个简单的例子。真正的逻辑要复杂得多。一般来说,如果你发现自己做的是一次性的。空的;那么你几乎肯定做错了什么。相关:延迟是为了模拟每个获取的文件正在发生的异步工作。你的解决方案的语义稍有不同,还不确定它是否适合我。在原始示例中,文件枚举器不会继续处理下一个文件,直到使用Task.Delay过度模拟了某些异步处理。在您的代码中,请删除Take50,因为这个数字没有任何意义,我可以在TakeWhile之后添加SelectMany来运行异步后处理,但它不会相同,因为文件枚举器将获取更多的文件,由于即将到来的超时,其中大部分将不会进行后期处理。还有一件事。如果我在TakeWhile之后添加post-processing,它将并发运行并且完全不受限制,这也是语义上的一个变化,与原始版本相比,post-processing是异步的,但不是并发的。但是假设它是好的,并且我们确实需要一个并发的后处理。事实上,它是完全不受限制的,如果我使用Select+Mergeint对它进行限制,那么对GetFileSource的调用将不会遵守超时,因为我们首先获取了这么多的文件名。再说一次,我得想想这对我是否有好处。这是一个很棒的方法。
var source = GetFileSource(path, x => {/*Do some async task here*/; return x; })
.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(1));