触发自定义类事件后,表单不会更新

时间:2010-09-24 20:03:40

标签: vb.net multithreading class process event-handling

我遇到的问题是,即使我看到事件触发,我的主表单也没有更新。让我解释一下这种情况并分享我的一些代码,因为我是业余爱好者,我肯定会很恐怖。

我创建了一个类来接受在后台运行进程的设置。我在该类中添加了一些自定义事件,因此我可以在我的表单中使用它而不是计时器。

我在处理这些事件的两个潜艇上休息一下,我看到它们在安装开始后立即开始。

我查看数据并且它正在发生并且没有抛出异常。

起初我认为这是因为datagridview存在一些延迟问题。我通过我发现的一些技巧将其设置为双缓冲,但这并不重要。在数据网格中出现数据之前,仍有大约10秒的延迟。

我考虑过它并决定我真的不需要datagridview并用多行文本框替换控件,但它并没有什么区别。显示表单/文本框的更新仍需要10秒或更长时间。

我在下面提供了一些代码。

Public Shared WithEvents np As NewProcess

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Try
            np = New NewProcess
            AddHandler np.InstallFinished, AddressOf np_InstallFinished
            AddHandler np.InstallStarted, AddressOf np_InstallStarted
        Catch ex As Exception

        End Try

    End Sub

Protected Sub np_InstallFinished(ByVal Description As String, ByVal ExitCode As Integer)
    InstallInProcess = False

    If Not Description = Nothing Then
        If Not ExitCode = Nothing Then
            AddLog(String.Format("Completed install of {0} ({1}).", Description, ExitCode))
        Else
            AddLog(String.Format("Completed install of {0}.", Description))
        End If
    End If
    RefreshButtons()
    UpdateListofApps()
    np.Dispose()
End Sub

Protected Sub np_InstallStarted(ByVal Description As String)
    InstallInProcess = True

    If Not Description = Nothing Then AddLog(String.Format("Started the install of {0}.", Description))
End Sub

Public Class NewProcess
    Dim ProcessName As String
    Dim ProcessVisibile As Boolean
    Dim Arguments As String
    Dim WaitforExit As Boolean
    Dim Description As String
    Dim ShellExecute As Boolean
    Dim EC As Integer = Nothing 'Exit Code
    Private IsBusy As Boolean = Nothing
    Dim th As Threading.Thread

    Public Event InstallFinished(ByVal Description As String, ByVal ExitCode As Integer)

    Public Event InstallStarted(ByVal Description As String)

    Public Function Busy() As Boolean
        If IsBusy = Nothing Then Return False
        Return IsBusy
    End Function

    Public Function ExitCode() As Integer
        Return EC
    End Function

    Public Function ProcessDescription() As String
        Return Description
    End Function

    ''' <summary>
    ''' Starts a new multithreaded process.
    ''' </summary>
    ''' <param name="path">Path of the File to run</param>
    ''' <param name="Visible">Should application be visible?</param>
    ''' <param name="Arg">Arguments</param>
    ''' <param name="WaitforExit">Wait for application to exit?</param>
    ''' <param name="Description">Description that will show up in logs</param>
    ''' <remarks>Starts a new multithreaded process.</remarks>
    Public Sub StartProcess(ByVal path As String, ByVal Visible As Boolean, Optional ByVal Arg As String = Nothing, Optional ByVal WaitforExit As Boolean = False, Optional ByVal Description As String = Nothing)

        Try
            Me.ProcessName = path
            Me.ProcessVisibile = Visible
            If Arguments = Nothing Then Me.Arguments = Arg
            Me.Description = Description
            Me.WaitforExit = WaitforExit

            If IsBusy And WaitforExit Then
                MessageBox.Show("Another install is already in process, please wait for previous install to finish.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                Exit Sub
            End If

            If Not fn_FileExists(ProcessName) Then
                MessageBox.Show("Could not find file " & ProcessName & ".", "Could not start process because file is missing.", MessageBoxButtons.OK, MessageBoxIcon.Error)
                Exit Sub
            End If

            th = New Threading.Thread(AddressOf NewThread)

            With th
                .IsBackground = True
                If Not Description Is Nothing Then .Name = Description
                .Start()
            End With
        Catch ex As Exception

        End Try
    End Sub

    Private Sub NewThread()
        Dim p As Process

        Try
            p = New Process

            With p
                .EnableRaisingEvents = True
                .StartInfo.Arguments = Arguments
                .StartInfo.FileName = ProcessName
                .StartInfo.CreateNoWindow = ProcessVisibile
            End With

            If ProcessVisibile Then
                p.StartInfo.WindowStyle = ProcessWindowStyle.Normal
            Else
                p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
            End If

            p.Start()
            IsBusy = True
            RaiseEvent InstallStarted(Description)

            If WaitforExit Then
                Do While p.HasExited = False
                    Threading.Thread.Sleep(500)
                Loop
                IsBusy = False
                RaiseEvent InstallFinished(Description, p.ExitCode)
            End If

            EC = p.ExitCode  

        Catch ex As Exception

        End Try
    End Sub

    Public Sub Dispose()
        ProcessName = Nothing
        ProcessVisibile = Nothing
        Arguments = Nothing
        WaitforExit = Nothing
        Description = Nothing
        EC = Nothing
        InstallInProcess = Nothing
        th.Join()
        MemoryManagement.FlushMemory()
    End Sub

End Class

Sub AddLog(ByVal s As String)
    Try

        s = String.Format("[{0}] {1}", TimeOfDay.ToShortTimeString, s)

        Form1.tbLogs.AppendText(s & vbCrLf)

        Using st As New StreamWriter(LogFilePath, True)
            st.WriteLine(s)
            st.Flush()
        End Using

    Catch ex As Exception
    End Try
End Sub

有什么想法吗?我完全失去了。

我尝试过添加application.doevents,me.refresh和其他一些东西:(

3 个答案:

答案 0 :(得分:5)

    Form1.tbLogs.AppendText(s & vbCrLf)

标准VB.NET陷阱。 Form1是类名,而不是对表单的引用。不幸的是,VB.NET在VB6中实现了一个不合时宜的地方。但是当你使用线程时它会崩溃。您将获得自动创建的另一个表单对象,由于从未调用过Show()方法,因此该表单不可见。因为线程没有引发消息循环,所以死了作为一个doornail。

您需要将对用户正在查看的实际表单对象的引用传递给worker类。 Form1代码中Me的值。您还必须使用Control.Invoke,因为从另一个线程更新控件是不合法的。我建议您触发一个事件,而不是Form1可以订阅的事件,这样您的工作者类就不会感染UI的实现细节。

答案 1 :(得分:1)

一些建议:

  1. 首先让它在没有线程的情况下工作。 (具有讽刺意味的是,你称自己为业余爱好者,事实上我只是在我过了'业余'舞台后才学会这样做)
  2. 不要尝试从后台线程更新GUI控件。那是在windows中被禁止的。我不确定你做的是什么(没有VB大师),但它看起来确实如此。
  3. 使用.net BackgroundWorker类。它具有内置功能,可以从后台线程与主线程进行对话。这不是完美的,而是一个良好的开端。

答案 2 :(得分:1)

你让我指出了正确的方向。谢谢汉斯。这是我的解决方案:

Private Sub SetText(ByVal [text] As String)

    If Me.tbLogs.InvokeRequired Then
        Dim d As New SetTextCallback(AddressOf SetText)
        Me.Invoke(d, New Object() {[text]})
    Else
        Me.tbLogs.Text = [text]
    End If
End Sub

Private Sub np_InstallStarted(ByVal Description As String)
    InstallInProcess = True
    If Me.tbLogs.Text = "" Then
        SetText(String.Format("[{0}] Started the install of {1}.{2}", TimeOfDay.ToShortTimeString, Description, vbCrLf))
    Else
        SetText(tbLogs.Text & vbCrLf & String.Format("[{0}] Started the install of {1}.{2}", TimeOfDay.ToShortTimeString, Description, vbCrLf))
    End If

End Sub

Private Sub np_InstallFinished(ByVal [Description] As String, ByVal [ExitCode] As Integer)
    InstallInProcess = False

    If Not Description = Nothing Then
        If Not ExitCode = Nothing Then
            SetText(tbLogs.Text & vbCrLf & String.Format("[{0}] Completed install of {1} ({2}).{3}", TimeOfDay.ToShortTimeString, Description, ExitCode, vbCrLf))
        Else
            SetText(tbLogs.Text & vbCrLf & String.Format("[{0}] Completed install of {1}.{3}", TimeOfDay.ToShortTimeString, Description, vbCrLf))
        End If
    End If
    RefreshButtons()
    UpdateListofApps()
    np.Dispose()
End Sub

因此,当事件启动安装已经开始或结束时,我使用SetText更新原始表单上的日志。

问题是我将原帖作为“未注册用户”发布,所以现在我想找出一种方法来说明问题已得到解答。再次感谢您的帮助!