在窗口句柄创建错误之前,无法在控件上调用Invoke或BeginInvoke

时间:2012-02-24 15:53:48

标签: .net winforms com-interop multithreading

我已经好好打了几天。我正在vb.net中开发一个com对象来显示启动画面。这将从vb脚本和Powershell调用,以便在脚本中发生其他指令时向用户提供状态。我间歇性地收到这个错误,我们将不胜感激。

这是我用来帮助开发我的应用程序的链接。 http://msdn.microsoft.com/en-us/library/ms233639.aspx

Public Class Progress

Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress
Private Delegate Sub FormDelegate()
Private ShowDelegate As FormDelegate
Private UnLdDelegate As FormDelegate
Private t As Thread

Public Sub New()

    MyBase.New()

    If IsNothing(appContext) Then
        frmSplash = New frmProgress
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()
        ShowDelegate = New FormDelegate(AddressOf frmSplash.Show)
        HideDelegate = New FormDelegate(AddressOf frmSplash.Hide)
        UnLdDelegate = New FormDelegate(AddressOf frmSplash.Close)
    End If

 End Sub

消息循环

 Private Sub StartMessageLoop()
    Application.Run(appContext)
 End Sub

ShowForm

Public Sub Show()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    End If
End Sub

卸载

 Public Sub Unload()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    End If
End Sub

线程退出事件

 Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

当我从powershell脚本调用Show Method时,通常会抛出错误。任何帮助将不胜感激。感谢

完整代码

Private ShowLock As New ManualResetEvent(False)
Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress

Private Delegate Sub FormDelegate()
Private Delegate Sub DisplayDelegate(ByVal msg As String)
Private Delegate Sub FormSettings(ByVal val As Boolean)

Private ShowDelegate As FormDelegate
Private HideDelegate As FormDelegate
Private UnLdDelegate As FormDelegate

Public Sub New()

    MyBase.New()
    frmSplash = New frmProgress

End Sub

Public Sub SetDisplayText1(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub


Public Sub SetDisplayText2(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText2)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label2.Text = msg
        frmSplash.Label2.Refresh()
    End If

End Sub

Public Sub SetTitle(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTitle)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Text = msg
    End If
End Sub


Public WriteOnly Property AlwaysOnTop() As Boolean

    Set(ByVal value As Boolean)
        SetTop(value)
    End Set
End Property


Public Sub Show()

    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated

    If IsNothing(appContext) Then
        Dim t As Thread
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()
    Else
        ShowLock.WaitOne()
        If frmSplash.InvokeRequired Then
            If frmSplash.IsHandleCreated = True Then
                ShowDelegate = New FormDelegate(AddressOf Show)
                appContext.MainForm.Invoke(ShowDelegate)
            End If
        Else
            frmSplash.Show()
        End If

    End If
End Sub

Public Sub SetTop(ByVal val As Boolean)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTop)
        frmSplash.Invoke(d, New Object() {[val]})
    Else
        frmSplash.TopMost = val
        frmSplash.TopLevel = val
    End If
End Sub

Public Sub Hide()

    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            HideDelegate = New FormDelegate(AddressOf Hide)
            appContext.MainForm.Invoke(HideDelegate)
        End If
    Else
        frmSplash.Hide()
    End If

End Sub

Public Sub Unload()

    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            UnLdDelegate = New FormDelegate(AddressOf Unload)
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    Else
        frmSplash.Close()
    End If

End Sub

Private Sub StartMessageLoop()
    Application.Run(appContext)
End Sub

Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

Protected Overrides Sub Finalize()
    MyBase.Finalize()
End Sub

Public Sub frm_HandleCreated()
    ShowLock.Set()
End Sub

My Debug Lines会让我相信崩溃发生在这里

 Public Sub Show()

    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated

    If IsNothing(appContext) Then
        Dim t As Thread
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()

感谢目前为止提供的所有帮助。

2 个答案:

答案 0 :(得分:3)

创建MainForm后,在最终成为“UI线程”的同一线程上访问其Handle属性。将它分配给一些一次性变量,重点只是访问它。 WinForms懒惰地初始化Handle,因此在访问该属性或首次显示该属性之前它无效。 InvokeRequired通过查看创建Handle的线程来工作,所以如果尝试在后台线程上懒惰地初始化它,可能会发生不好的事情。

有关详细信息,请参阅this question and answer

这可能不是它,但它确实听起来像我之前遇到过的东西。希望它能让你走上正轨。

答案 1 :(得分:2)

好的,我明白了问题所在。我的猜测是你正在调用PS脚本中的SetDisplayText方法之一。您需要保护那些InvokeRequiredIsHandleCreated上的支票,并且需要围绕表单的初始显示提供同步。我的VB.NET有点生疏,但我会尽我所能:

首先,将其添加为局部变量:

Private ShowLock As New ManualResetEvent(False);

然后,在HandleCreated上为frmSplash事件添加处理程序,并在该处理程序中添加:

Public Sub Form_HandleCreated(...)
     ShowLock.Set
End Sub

然后在你的ShowForm方法中,等待那个人设置:

Public Sub Show()
    ShowLock.WaitOne
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    End If
End Sub

这样你就可以确定在第一个线程尝试显示它之前已经创建了表单的句柄(或者做了其他的事情)。

最后,保护那些SetXXX方法(对于触及表单并可能从脚本调用的任何内容执行此操作):

Public Sub SetDisplayText1(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New SetDisplayText1Delegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub

编辑:试试这个 - 我从上面拿了你的代码并重新做了一点。

Private ShowLock As New ManualResetEvent(False)
Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress

Private Delegate Sub FormDelegate()
Private Delegate Sub DisplayDelegate(ByVal msg As String)
Private Delegate Sub FormSettings(ByVal val As Boolean)

Private ShowDelegate As FormDelegate
Private HideDelegate As FormDelegate
Private UnLdDelegate As FormDelegate

Public Sub New()

    MyBase.New()
    frmSplash = New frmProgress
    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated
    Dim t As Thread
    appContext = New ApplicationContext(frmSplash)
    t = New Thread(AddressOf StartMessageLoop)
    t.IsBackground = True
    t.SetApartmentState(ApartmentState.STA)
    t.Start()

End Sub

Public Sub SetDisplayText1(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub


Public Sub SetDisplayText2(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText2)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label2.Text = msg
        frmSplash.Label2.Refresh()
    End If

End Sub

Public Sub SetTitle(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTitle)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Text = msg
    End If
End Sub

Public WriteOnly Property AlwaysOnTop() As Boolean
    Set(ByVal value As Boolean)
        SetTop(value)
    End Set
End Property

Public Sub Show()
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            ShowDelegate = New FormDelegate(AddressOf Show)
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    Else
        frmSplash.Show()
    End If
End Sub

Public Sub SetTop(ByVal val As Boolean)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTop)
        frmSplash.Invoke(d, New Object() {[val]})
    Else
        frmSplash.TopMost = val
        frmSplash.TopLevel = val
    End If
End Sub

Public Sub Hide()
    ShowLock.WaitOne()    
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            HideDelegate = New FormDelegate(AddressOf Hide)
            appContext.MainForm.Invoke(HideDelegate)
        End If
    Else
        frmSplash.Hide()
    End If

End Sub

Public Sub Unload()
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            UnLdDelegate = New FormDelegate(AddressOf Unload)
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    Else
        frmSplash.Close()
    End If

End Sub

Private Sub StartMessageLoop()
    Application.Run(appContext)
End Sub

Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

Protected Overrides Sub Finalize()
    MyBase.Finalize()
End Sub

Public Sub frm_HandleCreated()
    ShowLock.Set()
End Sub