使用异步函数订阅可观察序列

时间:2016-05-10 04:15:48

标签: c# async-await system.reactive

我有一个asnyc函数,我希望在IObservable序列中的每个观察中调用它,一次限制一个事件的传递。消费者希望飞行中不超过一条消息;如果我理解正确的话,这也是RX合约。

考虑这个样本:

static void Main() {
  var ob = Observable.Interval(TimeSpan.FromMilliseconds(100));
  //var d = ob.Subscribe(async x => await Consume(x));  // Does not rate-limit.
  var d = ob.Subscribe(x => Consume(x).Wait());
  Thread.Sleep(10000);
  d.Dispose();
}

static async Task<Unit> Consume(long count) {
  Console.WriteLine($"Consuming {count} on thread {Thread.CurrentThread.ManagedThreadId}");
  await Task.Delay(750);
  Console.WriteLine($"Returning on thread {Thread.CurrentThread.ManagedThreadId}");
  return Unit.Default;
}

Consume函数伪造750毫秒的处理时间,ob每100毫秒产生一次事件。上面的代码有效,但在随机线程上调用task.Wait()。如果我改为在注释掉的第3行中订阅,那么Consume将以ob生成事件的相同速率调用(我甚至无法理解我正在使用的Subscribe的重载在这个评论的声明中,所以这可能是无稽之谈。

那么如何从可观察序列到async函数一次正确地传递一个事件呢?

1 个答案:

答案 0 :(得分:9)

订阅者不应该长时间运行,因此不支持在Subscribe处理程序中执行长时间运行的异步方法。

相反,请将您的异步方法视为单个值可观察序列,该序列从另一个序列中获取值。 现在你可以编写序列,这就是Rx的目的。

现在你已经实现了这一飞跃,你可能会有类似@Reijher在Howto call back async function from rx subscribe?中创建的内容。

他的代码细分如下。

//The input sequence. Produces values potentially quicker than consumer
Observable.Interval(TimeSpan.FromSeconds(1))
      //Project the event you receive, into the result of the async method
      .Select(l => Observable.FromAsync(() => asyncMethod(l)))
      //Ensure that the results are serialized
      .Concat()
      //do what you will here with the results of the async method calls
      .Subscribe();

在此方案中,您将创建隐式队列。 在生产者比消费者更快的任何问题中,需要使用队列在等待时收集值。 我个人更喜欢通过将数据放入队列来使其显式化。 或者,你可以明确地使用一个调度程序来发出信号,表明这应该是松弛的线程模型。

对于Rx新手来说,这似乎是一个流行的障碍(在订阅处理程序中执行异步)。 引导的原因有很多,不能将它们放入您的订户中,例如:  1.打破错误模型  2.你正在混合异步模型(rx在这里,任务那里)  3. subscribe是异步序列组合的消费者。异步方法只是一个单独的值序列,因此该视图不能是序列的结尾,但它的结果可能是。

<强>更新

为了说明关于打破错误模型的评论,这是OP样本的更新。

void Main()
{
    var ob = Observable.Interval(TimeSpan.FromMilliseconds(100));
    var d = ob.Subscribe(
        x => ConsumeThrows(x).Wait(),
        ex=> Console.WriteLine("I will not get hit"));

    Thread.Sleep(10000);
    d.Dispose();
}

static async Task<Unit> ConsumeThrows(long count)
{
    return await Task.FromException<Unit>(new Exception("some failure"));
    //this will have the same effect of bringing down the application.
    //throw new Exception("some failure");
}

在这里我们可以看到,如果要抛出OnNext处理程序,那么我们不受Rx OnError处理程序的保护。 该异常将无法处理,很可能会导致应用程序失效。