如何在Observable.Do扩展方法中传递异步方法?

时间:2014-08-26 20:12:19

标签: system.reactive

假设:

  1. IObservable<T> src
  2. async Task F(T){...}
  3. F只能按顺序调用。所以await F(x);await F(y);很好,但Task.Factory.ContinueWhenAll(new[]{F(x),F(y)}, _ => {...});是错误的,因为F(x)F(y)不能同时运行。
  4. 我很清楚await src.Do(F)是错误的,因为它会同时运行F

    我的问题是如何正确地做到这一点?

2 个答案:

答案 0 :(得分:6)

Observable.Do仅用于副作用,不用于顺序组合。 SelectMany可能是你想要的。从Rx 2.0开始,SelectMany存在重载,这使得使用Task<T>组合可观察对象变得非常容易。 (请注意这些以及类似的Task / Observable coop运算符可能引入的additional concurrency。)

var q = from value in src
        from _ in F(value.X).AsVoidAsync()  // See helper definition below
        from __ in F(value.Y).AsVoidAsync()
        select value;

但是,根据您明确询问Do运算符的事实,我怀疑 src 可能包含多个值,并且您不希望重叠调用每个值都 F 。在这种情况下,请考虑SelectMany实际上就像Select->Merge;因此,你可能想要的是Select->Concat

// using System.Reactive.Threading.Tasks

var q = src.Select(x => Observable.Defer(() => F(x).ToObservable())).Concat();

请勿忘记使用Defer,因为F(x)

AsVoidAsync Extension

IObservable<T>需要 T ,而Task代表 void ,因此Rx的转化运算符要求我们获得{{来自Task<T>的1}}。我倾向于使用Rx的Task结构T

public static class TaskExtensions
{
  public static Task<Unit> AsVoidAsync(this Task task)
  {
    return task.ContinueWith(t =>
    {
      var tcs = new TaskCompletionSource<Unit>();

      if (t.IsCanceled)
      {
        tcs.SetCanceled();
      }
      else if (t.IsFaulted)
      {
        tcs.SetException(t.Exception);
      }
      else
      {
        tcs.SetResult(Unit.Default);
      }

      return tcs.Task;
    },
    TaskContinuationOptions.ExecuteSynchronously)
    .Unwrap();
  }
}

或者,您可以随时调用专门的System.Reactive.Unit方法。

答案 1 :(得分:1)

最简单的方法是使用TPL Dataflow,默认情况下会限制为单个并发调用,即使对于异步方法也是如此:

var block = new ActionBlock<T>(F);
src.Subscribe(block.AsObserver());
await block.Completion;

或者,您可以订阅一个自行节制的异步方法:

var semaphore = new SemaphoreSlim(1);
src.Do(async x =>
{
  await semaphore.WaitAsync();
  try
  {
    await F(x);
  }
  finally
  {
    semaphore.Release();
  }
});