Search code examples
c#asynchronousasync-awaitsystem.reactivedispose

Async Disposable.Create


Disposable.Create require an Action as parameter. The Action is run when the Rx subscription is being disposed.

When disposing a Rx subscription I’d like to run some asynchronous clean up code, however using async () => with Action is identical to async void, which I’d like to avoid. For more details on why I want to avoid this, see here.

Is it possible to create something like a Disposable.AsyncCreate, which accepts Func<Task> rather than Action. If so how should I use it as part of a CompositeDisposable?

Or are there other patterns for dealing with asynchronous Disposal?


Solution

  • You could do something like this. I'm still not sure how good an idea it is:

    public class DisposableAsync
    {
        private readonly IDisposable _disposable; 
        private readonly Func<Task> _asyncDisposalAction;
        public DisposableAsync(IDisposable disposable, Func<Task> asyncDisposalAction)
        {
            _disposable = disposable;
            _asyncDisposalAction = asyncDisposalAction;
        }
    
        public Task DisposeAsync()
        {
            _disposable.Dispose();
            return _asyncDisposalAction();
        }
    }
    
    public static class DisposableAsyncExtensions
    {
        public static DisposableAsync ToAsync(this IDisposable disposable, Func<Task> asyncDisposalAction)
        {
            return new DisposableAsync(disposable, asyncDisposalAction);
        }
    }
    

    You could then use it like this:

    async Task Go()
    {
    
        var o = Observable.Interval(TimeSpan.FromMilliseconds(100));
        var d = o
            .Subscribe(i => Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: {i}"))
            .ToAsync(async () =>
            {
                Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Dispose Beginning");
                await Task.Delay(1000);
                Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Dispose Complete");
            });
        Console.Read();
        var t = d.DisposeAsync();
        Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Outside task, waiting for dispose to complete");
        await t;
        Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Task Complete");
    
    }
    

    This solution wouldn't work with using() statements, and the class DisposableAsync should be robustified. Outside of that, I can't think of anything wrong with it, but I'm predisposed (ahem) against it though. Just feels kind of hacky.