Alternative to using async () => in Rx Finally

て烟熏妆下的殇ゞ 提交于 2019-12-11 07:16:23

问题


My understanding is that async void, should be avoided and that async () => is just async void in disguise when used with Action.

Hence, using the Rx.NET Finally operator asynchronously with async () => should be avoided since Finally accepts Action as parameter:

IObservable<T>.Finally(async () =>
{
    await SomeCleanUpCodeAsync();
};

However, if this is bad practise, what is then best practice to use in the case where I need to asynchronously close a network coOnCompleted matter if my observable is OnError or OnCompleted?


回答1:


My understanding is that async void, should be avoided and that async () => is just async void in disguise.

This is partially wrong. async () => can either match Func<Task> (good) or Action (bad). The main reason for good/bad is that an exception that occurs in a async void call crashes the process, whereas a async Task exception is catchable.

So we just need to write an AsyncFinally operator that takes in a Func<Task> instead of an Action like Observable.Finally:

public static class X
{
    public static IObservable<T> AsyncFinally<T>(this IObservable<T> source, Func<Task> action)
    {
        return source
            .Materialize()
            .SelectMany(async n =>
            {
                switch (n.Kind)
                {
                    case NotificationKind.OnCompleted:
                    case NotificationKind.OnError:
                        await action();
                        return n;
                    case NotificationKind.OnNext:
                        return n;
                    default:
                        throw new NotImplementedException();
                }
            })
            .Dematerialize()
        ;
    }
}

And here's a demonstration of usage:

try
{
    Observable.Interval(TimeSpan.FromMilliseconds(100))
        .Take(10)
        .AsyncFinally(async () =>
        {
            await Task.Delay(1000);
            throw new NotImplementedException();
        })
        .Subscribe(i => Console.WriteLine(i));
}
catch(Exception e)
{
    Console.WriteLine("Exception caught, no problem");
}

If you swap out AsyncFinally for Finally, you'll crash the process.




回答2:


It is in Rx as it is elsewhere; avoid async void like the plague. In addition to the problems listed in the article, using asynchronous code in the synchronous operators "breaks" Rx.

I'd consider using OnErrorResumeNext() for cleaning up resources asynchronously. OnErrorResumeNext() let's you specify an observable which will run after the first, regardless the reason it ended:

var myObservable = ...

myObservable
    .Subscribe( /* Business as usual */ );

Observable.OnErrorResumeNext(
        myObservable.Select(_ => Unit.Default),
        Observable.FromAsync(() => SomeCleanUpCodeAsync()))
    .Subscribe();

myObservable would preferably be a ConnectableObservable (e.g. Publish()) to prevent multiple subscriptions.




回答3:


The method signature for Finally is

public static IObservable<TSource> Finally<TSource>(
    this IObservable<TSource> source,
    Action finallyAction
)

which expects an action, not a Task.

As an addendum, if you want to run something asynchronously, instead of async void, use Task.Factory methods inside the method so the intention is explicit.



来源:https://stackoverflow.com/questions/45205132/alternative-to-using-async-in-rx-finally

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!