

Async Disposables The Easy Way
source link: http://haacked.com/archive/2021/12/10/async-disposables/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Async Disposables The Easy Way
In the System.Reactive.Disposables
namespace (part of Reactive Extensions), there’s a small and useful Disposable
class. It has a Create
method that takes in an Action
and returns an IDisposable
instance. When that instance is disposed, the action is called. It’s a nice way of creating an ad-hoc IDisposable
. I use them often for creating a scope in code where something should happen at the end of the scope. Here’s an exceedingly trivial example:
Console.WriteLine("Working on it...");
using var scope = Disposable.Create(
() => Console.WriteLine("Done!"))
{
// Do stuff.
} // scope is disposed and Working on it... is printed to console
As trivial as it is, it’s still a little cleaner than the longer form:
Console.WriteLine("Working on it...");
try {
// Do stuff.
}
finally {
Console.WriteLine("Done!")
}
It is real handy when implementing a method that returns an IDisposable
. Rather than creating a custom class that inherits IDisposable
, you can return Disposable.Create
.
Here’s the code for Disposable
we use in Abbot.
using System;
using System.Threading;
namespace Serious;
/// <summary>
/// Provides a set of static methods for creating Disposables.
/// This is based off of
/// https://docs.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh229792(v=vs.103)
/// </summary>
public static class Disposable
{
/// <summary>
/// Creates the disposable that invokes the specified action when disposed.
/// </summary>
/// <param name="onDispose">The action to run during IDisposable.Dispose.</param>
/// <returns>The disposable object that runs the given action upon disposal.</returns>
public static IDisposable Create(Action onDispose) => new ActionDisposable(onDispose);
class ActionDisposable : IDisposable
{
volatile Action? _onDispose;
public ActionDisposable(Action onDispose)
{
_onDispose = onDispose;
}
public void Dispose()
{
Interlocked.Exchange(ref _onDispose, null)?.Invoke();
}
}
}
What about Async methods?
A limitation of IDisposable
is that the Dispose
method is not async. Suppose you need to make a web request at the end of the scope. You don’t want to use Dispose.Create
for that. Fortunately, C# 8.0 introduced the IAsyncDisposable
interface for these kind of scenarios.
So naturally, I wrote an async analog to Disposable
called AsyncDisposable
. Yes, I’m good at naming things.
Here’s an example of where we use it. When someone issues a command to Abbot in chat, we want Abbot to add a reaction to the user’s message to let the user know Abbot is working on it. When Abbot responds, we remove the reaction. So the code looks something like this (simplified).
await AddReactionAsync(message, "robot_face");
await using var scope = AsyncDisposable.Create(async () => RemoveReactionAsync(message, "robot_face"))
{
// Handle the incoming message
} // The reaction is removed here.
It makes the code so much cleaner this way.
If you try Abbot today, you won’t see this functionality yet because Slack takes 4-6 weeks (or longer) to approve new permissions. We’re waiting with fingers crossed and bated breaths.
Here’s the code for AsyncDisposable
.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Serious.Tasks;
/// <summary>
/// Helper class for creating an asynchronous scope.
/// A scope is simply a using block that calls an async method
/// at the end of the block by returning an <see cref="IAsyncDisposable"/>.
/// This is the same concept as
/// the <see cref="Disposable.Create"/> method.
/// </summary>
public static class AsyncDisposable
{
/// <summary>
/// Creates an <see cref="IAsyncDisposable"/> that calls
/// the specified method asynchronously at the end
/// of the scope upon disposal.
/// </summary>
/// <param name="onDispose">The method to call at the end of the scope.</param>
/// <returns>An <see cref="IAsyncDisposable"/> that represents the scope.</returns>
public static IAsyncDisposable Create(Func<ValueTask> onDispose)
{
return new AsyncScope(onDispose);
}
class AsyncScope : IAsyncDisposable
{
Func<ValueTask>? _onDispose;
public AsyncScope(Func<ValueTask> onDispose)
{
_onDispose = onDispose;
}
public ValueTask DisposeAsync()
{
return Interlocked.Exchange(ref _onDispose, null)?.Invoke() ?? ValueTask.CompletedTask;
}
}
}
I hope you find this useful in your own projects.
UPDATE: 2021-12-11 Based on Johaness comment, I made Dispose
idempotent.
Recommend
-
16
Learn Kubernetes the Hard Way (the Easy and Cheap Way) tl;dr Building on Kelsey Hightower’s f...
-
10
Pianojacq, an easy way to learn to play the piano October 16, 2020 I’m pretty sure that the hardest choice in my life to date has been the one between my two loves: music on the hand and computer programming on...
-
11
Fighting Mailman Subscription Spam: The Easy Way I recently noticed that both of the Mailman setups that I am running are being abused for subscription spam: Bots would automatically attempt to subscribe foreign email addres...
-
10
Introduction A little while ago I was having some issues with a particular piece of code that was making a fairly large numb...
-
11
SHEN CHAUHAN codes with a British accent ...
-
13
Advent of Code 2020 Day 19Advent of Code 2020 Day 19: An Easy way to do Part 2 12/19/20 A fun mechanic in AOC is that the problem is divided into two parts, with t...
-
12
github, tips An Easy Way to Track New Releases on GitHub ...
-
12
An Easy Way to Detect Clicks Outside an Element in Vue Dec 27 2018 | By Taha Shashtari Most of the web apps we use or create these days are full of elements that open as popups and close...
-
5
Felipe Silva August 26, 2021 5 minute read ...
-
9
Is there a way to have ASYNC MessageBox? advertisements Or do I have to use threads? (C++) ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK