C# 使用.NET类库显示表单以响应COM事件会冻结表单

C# 使用.NET类库显示表单以响应COM事件会冻结表单,c#,.net,vb.net,com,C#,.net,Vb.net,Com,我正在创建一个.NET组件,用于简化与支持COM自动化接口的应用程序的工作。该组件的一个特性是,它将显示对话框以从用户处收集信息。但是,在我需要使用的工作流中,对话框部分显示,然后冻结 我用Excel创建了一个简化版。实际上,我正在使用另一个带有COM自动化接口的应用程序,但这个问题是一个一般性问题,可以使用Excel重现,这是大多数人应该有的。下面是对各种作品的简要描述。我用VisualBasic写这篇文章,但假设C会出现同样的问题,尽管我还没有测试过 .NET类库项目“MyCmd” 类别My

我正在创建一个.NET组件,用于简化与支持COM自动化接口的应用程序的工作。该组件的一个特性是,它将显示对话框以从用户处收集信息。但是,在我需要使用的工作流中,对话框部分显示,然后冻结

我用Excel创建了一个简化版。实际上,我正在使用另一个带有COM自动化接口的应用程序,但这个问题是一个一般性问题,可以使用Excel重现,这是大多数人应该有的。下面是对各种作品的简要描述。我用VisualBasic写这篇文章,但假设C会出现同样的问题,尽管我还没有测试过

.NET类库项目“MyCmd”

类别MyDef

创建MyDef类的实例时,它将获取并保存当前活动的Excel工作表。它还侦听Excel工作表对象的SelectionChange事件。作为对SelectionChange事件的响应,它创建一个“MyCmd”类的实例(如下所述),并触发一个自定义CommandCreated事件,传递创建的MyCmd对象

它还支持一个名为ForceEvent的公共方法,该方法的作用与SelectionChange事件相同。这用于使用不同的代码路径测试功能

Imports Microsoft.Office.Interop.Excel

Public Class MyDef
    Private WithEvents _sheet As Microsoft.Office.Interop.Excel.Worksheet
    Public Event CommandCreated(ByVal command As MyCmd)

    Public Sub New()
        _sheet = g_app.ActiveSheet
    End Sub

    Public Sub ForceEvent()
        Dim cmd As New MyCmd()
        RaiseEvent CommandCreated(cmd)
    End Sub

    Private Sub _sheet_SelectionChange(Target As Range) Handles _sheet.SelectionChange
        Me.ForceEvent()
    End Sub
End Class
类MyCmd

包含单个方法的类,该方法创建并显示对话框的新实例

Public Class MyCmd
    Private _form As Form = Nothing

    Public Sub ShowDialog()
        If _form Is Nothing Then
            _form = New CommandDialog
        End If

        _form.Visible = True
    End Sub
End Class
我正在测试的对话框是一个表单(名为“CommandDialog”),有几个按钮,后面没有代码。我只是测试表单是否按预期显示

Public Module Globals
    Private _app As Microsoft.Office.Interop.Excel.Application = Nothing

    Public Function g_app() As Object
        If Not _app Is Nothing Then
            Return _app
        Else
            Try
                _app = GetObject(, "Excel.Application")
            Catch ex As Exception
                MsgBox("Excel must be running")
                Return Nothing
            End Try
            Return _app
        End If
    End Function
End Module
全局模块

我还使用下面的代码连接到Excel并获取Excel应用程序对象。它按预期工作

Public Module Globals
    Private _app As Microsoft.Office.Interop.Excel.Application = Nothing

    Public Function g_app() As Object
        If Not _app Is Nothing Then
            Return _app
        Else
            Try
                _app = GetObject(, "Excel.Application")
            Catch ex As Exception
                MsgBox("Excel must be running")
                Return Nothing
            End Try
            Return _app
        End If
    End Function
End Module
.NET类库项目“MyCmd”

我还有另一个名为“CommandFramework”的项目,它使用MyCmd类库。第二个项目是一个Windows窗体应用程序,它包含两个按钮和一个事件处理程序,用于MyDef支持的CommandCreated事件。整个代码如下

单击第一个按钮时,它将创建MyDef类的实例,由于WithEvent声明,该类还将为CommandCreated事件设置事件处理程序。单击第二个按钮调用ForceEvent方法,该方法将触发CommandCreated事件并显示对话框。我还可以单击活动Excel工作表中的一个单元格,这会导致触发SelectionChange事件并导致调用ForceEvent方法。但是,在这种情况下,对话框仅部分显示并被冻结。除了单击第二个按钮或接收Excel SelectionChange事件的触发器外,代码流是相同的。第二种情况导致冻结

Public Class CommandFramework
    Private WithEvents _cmdDef As MyCommand.MyDef = Nothing

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        _cmdDef = New MyCommand.MyDef
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        _cmdDef.ForceEvent()
    End Sub

    Private Sub cmdDef_CommandCreated(command As MyCommand.MyCmd) Handles _cmdDef.CommandCreated
        command.ShowDialog()
    End Sub
End Class

有人知道问题的原因是什么,解决方案是什么吗?我猜这是某种线程问题,出现了死锁,但我对这方面的了解还不够,甚至无法开始诊断或尝试其他方法。

尝试继承所有处理COM事件的类:

这将确保在最初创建.NET对象的同一线程(可能是主STA线程)上接收事件回调。看看这是否有帮助

注意,它仍然可能容易出现死锁或重入问题,这是COM编组的典型问题,特别是当您从Excel事件处理程序调用另一个Excel API时。理想情况下,您应该尽快从事件处理程序返回,并可能异步处理需要执行的任何操作,例如
SynchronizationContext.Current.Post()
wait Task.Yield()
。但是,对于异步场景,您仍然应该关心可能的可重入性,例如,当您仍在处理上一次调用时,事件处理程序再次被调用

我有一些相关的问答:


Excel应用程序事件到达辅助MTA线程。我怀疑这个问题是由于在这个次线程上创建了一个没有消息泵的新UI元素造成的。在调试它之前,你无法到达任何地方。从.NET项目中,使用项目>属性>调试,选择“启动外部程序”并选择Excel.exe。在Sub New上设置断点以确保调试器正在执行其任务。当它挂起时,使用Debug>breakall和Debug>Windows>Threads来选择正确的线程,并充分担心代码在错误的线程上运行。堆栈跟踪是获得帮助的基本信息。@HansPassant感谢您的输入。我不知道“全部中断”,然后调试线程。当我遍历有效的代码路径时,最终只得到一个主线程。但是,对于表单冻结的代码路径,还有一个额外的“工作线程”。对于该线程,它给我的唯一信息是ID、托管ID和类别。因为名字写的是“”,位置写的是“”。这似乎证实了这是一个线程问题。有没有关于下一步尝试什么的建议?@TnTinMn这听起来是个合理的答案。对于如何最终在主线程上处理该事件,您有什么建议吗?@BrianEkins,如果是线程问题,请参阅:以获取有关调用模式的信息。您可能还想了解由WinForm控件实现的。