IDisposable.Dispose()未在异步方法的发布模式下调用

时间:2015-12-22 18:09:10

标签: .net vb.net async-await

我在VS2015.1上使用.NET 4.6.1在VB.NET 14中编写了以下WPF示例应用程序:

Class MainWindow

    Public Sub New()
        InitializeComponent()
    End Sub

    Private Async Sub Button_Click(sender As Object, e As RoutedEventArgs)
        MessageBox.Show("Pre")

        Using window = New DisposableWindow()
            window.Show()

            For index = 1 To 1
                Await Task.Delay(100)
            Next
        End Using

        MessageBox.Show("Post")
    End Sub

    Class DisposableWindow
        Inherits Window
        Implements IDisposable

        Public Sub Dispose() Implements IDisposable.Dispose
            Me.Close()
            MessageBox.Show("Disposed")
        End Sub
    End Class

End Class

以下示例产生以下输出:

  • 调试模式:Pre,Disposed,Post
  • 发布模式:Pre,Post

这很奇怪。为什么Debug模式执行此代码的方式与Release模式不同??

当我将using块更改为手动try / finally块时,对window.Dispose()的调用甚至会抛出NullReferenceException:

Dim window = New DisposableWindow()
Try
    window.Show()

    For index = 1 To 1
        Await Task.Delay(100)
    Next
Finally
    window.Dispose()
End Try

还有更奇怪的东西:当排除for循环时,样本完美无缺。我只让For-loop运行一次,以指定产生问题的最小循环量。也可以随意使用While循环替换For循环。它产生与For循环相同的行为。

作品:

Using window = New DisposableWindow()
    window.Show()

    Await Task.Delay(100)
End Using

现在你可能会想:'那很奇怪!'。情况变得更糟。 我也在C#(6)中做了完全相同的例子。所以在C#中,Debug和Release模式都会导致'Pre,Disposed,Post'作为输出。

样本可以在这里下载:

http://www.filedropper.com/vbsample

http://www.filedropper.com/cssample

我在这一点上很难过。这是.NET Framework的VB.NET堆栈中的错误吗?或者我是否想要完成一些奇怪的事情,运气似乎是C#中的工作,部分是VB.NET中的工作?

修改

做了更多测试:

  • 在VB.NET中针对发布模式禁用编译器优化,使其行为类似于调试模式(正如预期的那样,但想要测试它,以防万一)。
  • 当我定位.NET 4.5(async / await可用的最早版本)时,问题也会发生。

更新

此后已经修复。版本1.2计划公开发布,但主分支中的最新版本应包含修复程序。

请参阅:https://github.com/dotnet/roslyn/issues/7669

1 个答案:

答案 0 :(得分:12)

我会写这个,这个Roslyn错误是非常讨厌的,并且可能打破很多VB.NET程序。以一种非常丑陋且难以诊断的方式。

这个bug很难看到,你必须用反编译器查看生成的程序集。我会以惊人的速度描述它。 Async Sub中的语句被重写为状态机,代码段中的特定类名是VB $ StateMachine_1_buttonClick。你只能用一个体面的反编译器来看它。此类的MoveNext()方法执行方法体中的语句。在您的异步代码运行时,会多次输入此方法。

MoveNext()使用的变量需要捕获,将本地变量转换为类的字段。与您的window变量一样,稍后当Using语句结束并且需要调用Dispose()方法时将需要它。 Debug版本中此变量的名称为$VB$ResumableLocal_window$0。当您构建程序的Release版本时,编译器会尝试优化此类并且会严重错误地进行编译。它消除捕获并使window成为MoveNext()的局部变量。这是非常错误的,当执行在Await之后恢复时,该变量将是Nothing。因此,它的Dispose()方法不会被调用。

这个Roslyn错误具有非常大的影响,它将破坏在语句体包含Await的Async方法中使用Using语句的任何VB.NET代码。这不容易诊断,丢失的Dispose()调用经常不被发现。除了像你这样的情况,它有一个非常明显的副作用。生产中必须有很多程序现在都有这个bug。副作用是他们会跑得很重,消耗的资源超过必要的资源。该程序可能会以许多难以诊断的方式失败。

此错误有一个临时解决方法,请务必永远不要部署VB.NET应用程序的Debug版本,这有其他问题。请关闭优化程序。选择发布版本并使用项目>属性>编译标签>高级编译选项>取消"启用优化"复选框。

哎呀,这很糟糕。