当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《IDisposable.Dispose()未在异步方法的发布模式下调用》经验,为你挑选了1个好方法。

我在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> Hans Passant..:

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

这个bug有一个临时的解决方法,一定要永远不要部署你的VB.NET应用程序的Debug版本,这有其他问题.请关闭优化程序.选择Release构建并使用Project> Properties> Compile选项卡> Advanced Compile Options>取消选中"Enable optimizations"复选框.

哎呀,这很糟糕.


这非常难看.For/While循环可能对bug有帮助,我没有深入挖掘它来分析它的副作用.可能会解释为什么以前没有找到这个bug.重写Async Sub的Roslyn代码非常重要,这些代码转换是编译器必须做的最复杂的事情.
推荐阅读
可爱的天使keven_464
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有