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.