C# Rx.NET以什么方式生成可取消的可观察文件名?

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

我想生成一个可观察的文件,以便随时可以取消对文件名的发现。在本例中,取消将在1秒内自动进行

这是我目前的代码:

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));