问题
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 justasync 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