简体   繁体   English

如何在 Rx 中使用异步方法订阅?

[英]How to Subscribe with async method in Rx?

I have following code:我有以下代码:

IObservable<Data> _source;

...

_source.Subscribe(StoreToDatabase);

private async Task StoreToDatabase(Data data) {
    await dbstuff(data);
}

However, this does not compile.但是,这不会编译。 Is there any way how to observe data asynchronously?有没有办法异步观察数据? I tried async void , it works, but I feel that given solution is not feasible.我尝试了async void ,它有效,但我觉得给定的解决方案不可行。

I also checked Reactive Extensions Subscribe calling await , but it does not answer my question (I do not care about the SelectMany result.)我还检查了Reactive Extensions Subscribe 调用 await ,但它没有回答我的问题(我不关心SelectMany结果。)

You don't have to care about the SelectMany result.您不必关心SelectMany结果。 The answer is still the same... though you need your task to have a return type (ie Task<T> , not Task ).答案仍然相同......尽管您需要您的任务具有返回类型(即Task<T> ,而不是Task )。

Unit is essentially equivalent to void , so you can use that: Unit本质上等同于void ,因此您可以使用它:

_source.SelectMany(StoreToDatabase).Subscribe();

private async Task<Unit> StoreToDatabase(Data data)
{
    await dbstuff(data);
    return Unit.Default;
}

This SelectMany overload accepts a Func<TSource, Task<TResult> meaning the resulting sequence will not complete until the task is completed.SelectMany重载接受Func<TSource, Task<TResult>意味着结果序列在任务完成之前不会完成。

Late answer, but I think that the following extension methods correctly encapsulate what Charles Mager proposed in his answer :迟到的答案,但我认为以下扩展方法正确地封装了 Charles Mager 在他的回答中提出的内容:

public static IDisposable SubscribeAsync<T>(this IObservable<T> source, 
                         Func<Task> asyncAction, Action<Exception> handler = null)
{
    Func<T,Task<Unit>> wrapped = async t =>
    {
        await asyncAction();
        return Unit.Default;
    };
    if(handler == null)
        return source.SelectMany(wrapped).Subscribe(_ => { });
    else
        return source.SelectMany(wrapped).Subscribe(_ => { }, handler);
}

public static IDisposable SubscribeAsync<T>(this IObservable<T> source, 
                         Func<T,Task> asyncAction, Action<Exception> handler = null)
{
    Func<T, Task<Unit>> wrapped = async t =>
    {
        await asyncAction(t);
        return Unit.Default;
    };
    if(handler == null)
        return source.SelectMany(wrapped).Subscribe(_ => { });
    else
        return source.SelectMany(wrapped).Subscribe(_ => { }, handler);
}

I've been using TPL DataFlow to control back pressure and have used it to solve this problem.我一直在使用TPL DataFlow来控制背压,并用它来解决这个问题。

The key part is ITargetBlock<TInput>.AsObserver() - source .关键部分是ITargetBlock<TInput>.AsObserver() - source

// Set a block to handle each element
ITargetBlock<long> targetBlock = new ActionBlock<long>(async p =>
{
    Console.WriteLine($"Received {p}");
    await Task.Delay(1000);
    Console.WriteLine($"Finished handling {p}");
},
new ExecutionDataflowBlockOptions { BoundedCapacity = 1 });

// Generate an item each second for 10 seconds
var sequence = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10);

// Subscribe with an observer created from the target block.
sequence.Subscribe(targetBlock.AsObserver());

// Await completion of the block
await targetBlock.Completion;

The important part here is that the ActionBlock's bounded capacity is set to 1. This prevents the block from receiving more than one item at a time and will block OnNext if an item is already being processed!这里的重要部分是 ActionBlock 的有界容量设置为 1。这可以防止块一次接收多个项目,如果项目已经在处理, 则会阻止 OnNext

My big surprise here was that it can be safe to call Task.Wait and Task.Result inside your subscription.我最大的惊喜是在订阅中调用Task.WaitTask.Result是安全的。 Obviously, if you have called ObserverOnDispatcher() or similar you will probably hit deadlocks.显然,如果您调用了ObserverOnDispatcher()或类似方法,您可能会陷入僵局。 Be careful!小心!

So you want to run the Store Data Procedure, possibly some other procedure and asynchronously await the completion or partial result.所以你想运行存储数据过程,可能是其他一些过程,并异步等待完成或部分结果。 How about Create constructor shown here:此处显示的创建构造函数如何:

IObservable<Int32> NotifyOfStoringProgress = 
Observable.Create(
(Func<IObserver<Int32>, Task>)
(async (ObserverToFeed) =>
{
    ObserverToFeed.OnNext(-1);    
    Task StoreToDataBase = Task.Run(()=> {;});
    ObserverToFeed.OnNext(0);    
    ;;
    await StoreToDataBase;
    ObserverToFeed.OnNext(1);
    ;;
}));

NotifyOfStoringProgress.Subscribe(onNext: Notification => {;});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM