如何从rx订阅回调异步函数?

时间:2014-04-11 08:01:00

标签: c# asynchronous system.reactive reactive-programming

我想在Rx订阅中回拨一个异步函数。

E.g。那样:

public class Consumer
{
    private readonly Service _service = new Service();

    public ReplaySubject<string> Results = new ReplaySubject<string>();

    public void Trigger()
    {
        Observable.Timer(TimeSpan.FromMilliseconds(100)).Subscribe(async _ => await RunAsync());
    }

    public Task RunAsync()
    {
        return _service.DoAsync();
    }
}

public class Service
{
    public async Task<string> DoAsync()
    {
        return await Task.Run(() => Do());
    }

    private static string Do()
    {
        Thread.Sleep(TimeSpan.FromMilliseconds(200));
        throw new ArgumentException("invalid!");
        return "foobar";
    }
}

[Test]
public async Task Test()
{
    var sut = new Consumer();
    sut.Trigger();
    var result = await sut.Results.FirstAsync();
}

为了正确捕捉异常,需要做些什么?

3 个答案:

答案 0 :(得分:26)

保罗·贝茨的答案在大多数情况下都有效,但是如果你想在等待异步函数完成时阻塞流,你需要这样的东西:

Observable.Interval(TimeSpan.FromSeconds(1))
          .Select(l => Observable.FromAsync(asyncMethod))
          .Concat()
          .Subscribe();

或者:

Observable.Interval(TimeSpan.FromSeconds(1))
          .Select(_ => Observable.Defer(() => asyncMethod().ToObservable()))
          .Concat()
          .Subscribe();

答案 1 :(得分:17)

将其更改为:

Observable.Timer(TimeSpan.FromMilliseconds(100))
    .SelectMany(async _ => await RunAsync())
    .Subscribe();

订阅不会将异步操作保留在Observable中。

答案 2 :(得分:12)

您不希望将async方法传递给Subscribe,因为这会创建async void方法。尽力避免async void

在您的情况下,我认为您想要的是为序列的每个元素调用async方法,然后缓存所有结果。在这种情况下,使用SelectMany为每个元素调用async方法,并使用Replay缓存(加上Connect以获得滚动):

public class Consumer
{
    private readonly Service _service = new Service();

    public IObservable<string> Trigger()
    {
        var connectible = Observable.Timer(TimeSpan.FromMilliseconds(100))
            .SelectMany(_ => RunAsync())
            .Replay();
        connectible.Connect();
        return connectible;
    }

    public Task<string> RunAsync()
    {
        return _service.DoAsync();
    }
}

我改为从Results方法返回Trigger属性,我认为这更有意义,所以测试现在看起来像:

[Test]
public async Task Test()
{
    var sut = new Consumer();
    var results = sut.Trigger();
    var result = await results.FirstAsync();
}