I have a class with a resource that ideally should be disposed of using an async method, and I'm trying to use a Using
statement to do so.
Luckily, .NET added the IAsyncDisposable
interface as well as a nice doc explaining how to Implement a DisposeAsync method. The doc says:
The
public
parameterlessDisposeAsync()
method is called implicitly in anawait using
statement
Easy! Except... there's just one problem: I'm using VB.NET, not C#. I can't find any documentation on how to use this feature in VB.NET, or anyone asking after it. VB.NET (using .NET 6) will only allow a Using
statement for an instance of IDisposable
, and if both IDisposable
and IAsyncDisposable
are implemented, it only calls Dispose
, not DisposeAsync
. I can't find any equivalent Await Using
statement for VB.NET.
Is there any way for me to properly leverage the IAsyncDisposable
interface in VB.NET with a Using
statement? Or have I finally reached the point where Microsoft's abandonment of the language has caught up with me?
IAsyncDisposable
in Using
.Await
inside Finally
.using
part:Just create a new empty C# Class Library project with a single helper method which you can call from VB.NET, something like this:
(Just don't expect to be able to use ConfigureAwait(False)
- even in C# it's hard).
public static class ArghExtensions
{
public static async Task UsingAsyncDisposableAsync( this IAsyncDisposable subject, CancellationToken cancellationToken, Func<CancellationToken,Task> asyncBody ) // Need this parameter order for ergonomic reasons.
{
await using( subject )
{
// Argument validation (preconditions) needs to be done from within the implicit try/finally, otherwise `subject` won't be disposed if `asyncBody` is null.
if( subject is null ) throw new ArgumentNullException(nameof(subject));
if( asyncBody is null ) throw new ArgumentNullException(nameof(asyncBody));
await asyncBody( cancellationToken ).ConfigureAwait(false);
}
}
}
Then call it from VB.NET:
Imports ArghExtensions
Async Function FoobarAsync( cancellationToken As CancellationToken ) As Task
Dim d AS IAsyncDisposable = GetSomethingDisposable()
Await d.UsingAsyncDisposableAsync( Async Function( ct As CancellationToken )
' do async stuff in here
End Function )
End Function
An alternative way of implementing this method, without using any C# step, is to use Reflection.Emit from within VB.NET to generate equivalent (and safe) IL
that reimplements that UsingAsyncDisposableAsync
method - though this will be a lot of work for probably little gain tbh.
Instead of using any C# code, we can actually still do it all in VB.NET, with the caveat that you need to use Try/Catch
and not Try/Finally
as VB.NET still can't use Await
inside Finally
:
Async Function FoobarAsync( cancellationToken As CancellationToken ) As Task
' See https://stackoverflow.com/a/16626313/159145
Dim capturedException As ExceptionDispatchInfo = Nothing
Dim d AS IAsyncDisposable = GetSomethingDisposable()
Try
' do async stuff in here, before the DisposeAsync call below.
Await d.DisposeAsync().ConfigureAwait(False)
d = Nothing
Catch x As Exception
capturedException = ExceptionDispatchInfo.Capture( x )
End Try
If capturedException IsNot Nothing Then
If d IsNot Nothing Then
Await d.DisposeAsync().ConfigureAwait(False)
d = Nothing
End If
capturedException.Throw()
End If
End Function
Function GetSomethingDisposable() As IAsyncDisposable
Return New MemoryStream()
End Function
I haven't run this code through any static-analysis, but I'm confident this won't trigger CA2000