在VB.NET中,使用Using语句来处理IAsyncDisposable。

3
我有一个类,其中包含一个理想情况下应该使用异步方法进行处理的资源,并且我正在尝试使用Using语句来处理它。
幸运的是,.NET添加了IAsyncDisposable接口,并提供了一个很好的文档,解释了如何实现DisposeAsync方法。文档中说:

public无参数的DisposeAsync()方法会在await using语句中隐式调用。

简单!除了一个问题:我正在使用VB.NET,而不是C#。我找不到任何关于如何在VB.NET中使用这个功能的文档,也没有人询问过。VB.NET(使用.NET 6)只允许对IDisposable实例使用Using语句,如果同时实现了IDisposableIAsyncDisposable,它只调用Dispose而不是DisposeAsync。我找不到VB.NET的等效Await Using语句。

有没有办法让我在VB.NET中正确利用IAsyncDisposable接口并使用Using语句?或者我终于达到了微软放弃该语言的程度?


1
"或者我终于到了微软放弃该语言的那个点追上我的时候了吗?" - Dai
1
我试着使用Async扩展方法、Try/Finally和回调地狱来拼凑一些东西,但显然与C#不同,VB.NET仍然不允许在Finally块中使用Await(用于Await disposable.DisposeAsync())。有一些解决方案适用于C#具有相同限制的情况,这些解决方案也适用于VB.NET,但对我来说变得太繁琐了,所以我放弃了,很抱歉。因此,我认为这是一个更严肃地迁移到VB.NET之外的时机。 - Dai
关于这个问题应该有一个之前的提问(尽管我搜索了一下并没有找到,也许我记得的那个被删除了)。简短的回答是,没有内置的支持,所以你必须明确地编写在“Using”下发生的操作。 - Craig
1个回答

2
  • VB.NET(即使截至2023年8月)不支持在Using中使用IAsyncDisposable
  • 它也不支持在Finally中使用Await
  • 所以你能做的最好办法是尝试一些丑陋的技巧和/或将一些旧的C#技巧移植过来 some old C# tricks...

方法1:使用C#处理using部分:

只需创建一个新的空C#类库项目,其中包含一个可以从VB.NET调用的辅助方法,类似于以下内容:

(只是不要指望能够使用ConfigureAwait(False) - 即使在C#中也很困难)。

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);
        }
    }
}

然后从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

另一种实现这个方法的替代方式,而不使用任何C#步骤,是在VB.NET中使用Reflection.Emit来生成等效(且安全)的IL代码,重新实现UsingAsyncDisposableAsync方法 - 尽管这可能需要很多工作,但收益可能很小。

方法二:拥抱VB.NET的“冗长”

与其使用任何C#代码,我们实际上可以完全在VB.NET中完成所有操作,但需要注意的是,你需要使用Try/Catch而不是Try/Finally,因为VB.NET仍然无法在Finally块中使用Await

Async Function FoobarAsync( cancellationToken As CancellationToken ) As Task

    ' See https://dev59.com/P2Qn5IYBdhLWcg3wtpBW#16626313
    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

我还没有对这段代码进行任何静态分析,但我有信心这不会触发CA2000


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接