等待观察完成

时间:2017-02-14 16:49:34

标签: c# .net async-await system.reactive observable

我有一个使用observable异步工作的方法。我想知道使这个方法 async 的最佳方法是什么,这样我就可以等待,并在observable完成后做一些工作。

我的第一次尝试是在observable上使用 await

public async Task DoWorkAsync()
{
    var observable = Observable.Create<int>(o =>
    {
        Task.Run(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("OnNext");
            o.OnNext(1);
            o.OnError(new Exception("exception in observable logic"));
            //o.OnCompleted();
        });    
        return Disposable.Empty;
    });

    //observable = observable.Publish().RefCount();

    observable.Subscribe(i => Console.WriteLine(i));
    Console.WriteLine("Awaiting...");
    await observable;
    Console.WriteLine("After Awaiting...");
}

根据情况,我对该方法有不同的问题(+/-表示这部分代码未注释/注释):

  1. + OnNext + OnCompleted -OnError -RefCount: OnNext 被调用2次(可观察的订阅次数为2次)。这是我想避免的。

  2. + OnNext + OnCompleted -OnError + RefCount:当我使用 RefCount()方法时,代码可以正常工作。

  3. -OnNext + OnCompleted -OnError + RefCount:“Sequence is no element”当我的observable没有引发 OnNext 时抛出异常。

  4. + OnNext -OnCompleted + OnError -RefCount: OnNext 被调用了2次。提出例外。

  5. + OnNext -OnCompleted + OnError + RefCount:显示1后挂起(可能是因为它想要返回到等待的线程)。我们可以使用SubscribeOn(ThreadPoolScheduler.Instance)

  6. 使它工作(并引发异常)

    无论如何,当observable为空(没有 OnNext 上升)时,即使没有调用 OnError ,我们也会得到异常,并且我们在可观察逻辑中没有任何异常。这就是为什么等待观察不是好的解决方案。

    这就是我尝试使用TaskCompletionSource

    的另一种解决方案的原因
    public async Task DoWorkAsync()
    {
        var observable = Observable.Create<int>(o =>
        {
            Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("OnNext");
                o.OnNext(1);
                o.OnError(new Exception("exception in observable logic"));
                //o.OnCompleted();
            });
            return Disposable.Empty;
        });
    
        var tcs = new TaskCompletionSource<bool>();
    
        observable.Subscribe(i => Console.WriteLine(i),
        e =>
        {
            //tcs.TrySetException(e);
            tcs.SetResult(false);
        },
        () => tcs.TrySetResult(true));
    
        Console.WriteLine("Awaiting...");
        await tcs.Task;
        Console.WriteLine("After Awaiting...");
    }
    

    它适用于所有场景,如果调用 OnError ,我们可以使用tcs.SetResult(false)并且在外部方法中没有关于异常细节的信息,或者我们可以使用tcs .TrySetException(e)并且能够在外部方法中捕获异常。

    如果有更好/更清洁的解决方案,或者我的第二个解决方案是要走的路,你能建议我吗?

    修改

    所以我想知道是否有比我的第二个解决方案更好的解决方案:

    • 不需要使用 .Publish()。RefCount()
    • 不需要额外的订阅(在等待可观察的情况下会发生什么 - OnNext被调用2次)
    • 当然,我可以将我的解决方案包装在一些异步扩展方法中,以便订阅返回Task

3 个答案:

答案 0 :(得分:2)

修改

如果您删除订阅,则可以执行以下操作:

await observable.Do(i => Console.WriteLine(i)).LastOrDefaultAsync();

至于你的任意要求......对于冷观察者没有多个订阅是有道理的;所以你发布它。拒绝使用.Publish().Refcount()没有意义。我不明白为什么你拒绝解决问题的解决方案。

那里有很多,但我认为这是你的主要问题:

  

无论如何,如果observable为空(没有OnNext上升),我们得到   异常,即使没有调用OnError,我们也没有   可观察逻辑中的异常。这就是等待观察的原因   不好的解决方案。

await observableawait observable.LastAsync()相同。因此,如果没有元素,则会出现异常。想象一下,将该陈述更改为int result = await observable;如果没有元素,result的价值应该是多少?

如果您将await observable;更改为await observable.LastOrDefaultAsync();,一切都应该顺利进行。

是的,您应该使用.Publish().Refcount()

答案 1 :(得分:0)

我显然更喜欢第二种解决方案,因为它只订阅了一次。

但出于好奇:编写这样的方法的目的是什么? 如果允许可配置的副作用,这将是等效的:

public async Task DoWorkAsync()
{
    Action<int> onNext = Console.WriteLine;

    await Task.Delay(1000);
    onNext(1);
    throw new Exception("exception in DoWork logic"); // ... or don't
}

答案 2 :(得分:0)

您可以使用ToTask扩展方法:

await observable.ToTask();