e2579382
e2579382

Reputation: 55

Open a modal form from a background thread to block UI thread without also blocking background thread

Maybe this is a simple question and I just don't know the correct search terms to find the answer, but my Google-fu has failed me on this one.

My vb.net application has a background thread that controls all the socket communication. Occasionally, I need this communication thread to open up a modal form to display a message and block UI interaction until the communication thread completes a series of tasks at which point, the communication thread will remove the modal form, allowing the user to continue interaction.

Currently, my communications class containing the background thread has two events, StartBlockingTask and EndBlockingTask. My main form has event listeners for these events that call like-named subs. They call code looking like this:

Private Delegate Sub BlockingDelegate(ByVal reason As String)

Private Sub StartBlockingTask(ByVal reason As String)
    If Me.InvokeRequired Then
        Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
        Me.Invoke(del, New Object() {reason})
    Else
        Try
            _frmBlock.lblBlock.Text = reason
            _frmBlock.ShowDialog()
        Catch ex As Exception
            'stuff
        End Try
    End If
End Sub

Private Sub EndBlockingTask()
    If Me.InvokeRequired Then
        Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
        Me.Invoke(del, New Object() {""})
    Else
        Try
            If (Not _frmBlock Is Nothing) Then
                _frmBlock.DialogResult = Windows.Forms.DialogResult.OK
            End If
        Catch ex As Exception
            'stuff
        End Try
    End If
End Sub

This successfully blocks the UI from interaction, but it also blocks the communications thread so the EndBlockingTask event never actually gets raised. How can I open this modal dialog from the communications thread and allow the communications thread to still continue running?

Thanks in advance!

Upvotes: 1

Views: 2103

Answers (2)

Idle_Mind
Idle_Mind

Reputation: 39142

I disagree.

All that needs to be done is to change Invoke() to BeginInvoke() and you're golden.

This is because Invoke() is actually synchronous which causes it block until ShowDialog() resolves.

Using BeginInvoke() makes it asynchronous and allows the UI to be blocked while the thread continues:

Public Class Form1

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        If Not BackgroundWorker1.IsBusy Then
            BackgroundWorker1.RunWorkerAsync()
        End If
    End Sub

    Private Delegate Sub BlockingDelegate(ByVal reason As String)

    Private Sub StartBlockingTask(ByVal reason As String)
        If Me.InvokeRequired Then
            Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
            Me.BeginInvoke(del, New Object() {reason})
        Else
            Try
                _frmBlock.lblBlock.Text = reason
                _frmBlock.ShowDialog()
            Catch ex As Exception
                'stuff
            End Try
        End If
    End Sub

    Private Sub EndBlockingTask()
        If Me.InvokeRequired Then
            Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
            Me.BeginInvoke(del, New Object() {""})
        Else
            Try
                If (Not _frmBlock Is Nothing) Then
                    _frmBlock.DialogResult = Windows.Forms.DialogResult.OK
                End If
            Catch ex As Exception
                'stuff
            End Try
        End If
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        For i As Integer = 1 To 10
            BackgroundWorker1.ReportProgress(i)
            System.Threading.Thread.Sleep(1000)

            If i = 4 Then
                Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
                del("bada...")
            ElseIf i = 7 Then
                Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
                del("bing!")
            End If
        Next
    End Sub

    Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        Label1.Text = e.ProgressPercentage
    End Sub

End Class

Upvotes: 3

user3956566
user3956566

Reputation:

You are calling the address from within the sub it is created in. The Address needs to be called from outside this sub.

 Private Sub StartBlockingTask(ByVal reason As String)
    If Me.InvokeRequired Then
        Dim del As New BlockingDelegate(AddressOf StartBlockingTask)


   Private Sub EndBlockingTask()
        If Me.InvokeRequired Then
            Dim del As New BlockingDelegate(AddressOf EndBlockingTask)

You need to create two delegates. One for StartBlockingTask and one for EndBlockingTask

This is an example from MSDN,

Delegate Sub MySubDelegate(ByVal x As Integer)
Protected Sub Test()
   Dim c2 As New class2()
   ' Test the delegate.
   c2.DelegateTest()
End Sub

Class class1
   Sub Sub1(ByVal x As Integer)
      MessageBox.Show("The value of x is: " & CStr(x))
   End Sub
End Class

Class class2
   Sub DelegateTest()
      Dim c1 As Class1
      Dim msd As MySubDelegate
      c1 = New Class1()
      ' Create an instance of the delegate.
      msd = AddressOf c1.Sub1
      msd.Invoke(10) ' Call the method.
   End Sub
End Class

http://msdn.microsoft.com/en-us/library/5t38cb9x(v=vs.71).aspx

Let me know if this helps.

Upvotes: 1

Related Questions