Search code examples
vb.netmultithreadingbackgroundworker

Threading and Background Worker


I'm trying to make something that checks Xbox Usernames, but I tried to do so with a timer, which lags the UI of it. So I figured I do it with a background worker, but with the different threads, and calling UI references isn't really working out for me. Any help?

        For i As Integer = 0 To ListBox1.Items.Count
        Using Wc As New WebClient()
            Try
                Dim Xbox As String = String.Empty
                Xbox = Wc.DownloadString("http://www.xboxgamertag.com/search/" & ListBox1.SelectedItem.ToString())

                If Xbox.Contains("Online Status") Then
                    FlatAlertBox1.Text = "Gamertag " & ListBox1.SelectedItem.ToString & " is taken :("
                    FlatAlertBox1.kind = FlatAlertBox._Kind.Error
                    FlatAlertBox1.Visible = True
                End If

            Catch ex As Exception
                FlatAlertBox1.Text = "Gamertag " & ListBox1.SelectedItem.ToString & " is not taken!"
                FlatAlertBox1.kind = FlatAlertBox._Kind.Success
                FlatAlertBox1.Visible = True
            End Try

        End Using
    Next
    ListBox1.SelectedIndex += 1

When I try to run it, I get:

An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code

Additional information: Cross-thread operation not valid: Control 'ListBox1' accessed from a thread other than the thread it was created on.

If there is a handler for this exception, the program may be safely continued.

On the line : FlatAlertBox1.Text = "Gamertag " & ListBox1.SelectedItem.ToString & " is not taken!"


Solution

  • You can't update GUI controls on a non-UI thread, so you will have to try "reporting" the findings during your loop.

    Here I just start the thread with a copy of strings from the ListBox:

    bgw.RunWorkerAsync(ListBox1.Items.Cast(Of String))
    

    We need a simple class to report information:

    Public Class UserStatus
      Property Name As String
      Property Kind As Integer
    End Class
    

    Then in the DoWork method

    Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
      For Each s As String In DirectCast(e.Argument, IEnumerable(Of String))
        Using Wc As New WebClient()
          Dim Xbox As String = String.Empty
          Xbox = Wc.DownloadString("http://www.xboxgamertag.com/search/" & s)
          If Xbox.Contains("Online Status") Then
            bgw.ReportProgress(0, New UserStatus() With {.Name = s, .Kind = Kind.Error})
          Else
            bgw.ReportProgress(0, New UserStatus() With {.Name = s, .Kind = Kind.Success})
          End If
        End Using
      Next
    End Sub
    

    In the ProgressChanged event, you read your status:

    Private Sub bgw_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
      Dim us As UserStatus = DirectCast(e.UserState, UserStatus)
      If us.Kind = Kind.Success Then
        FlatAlertBox1.Text = "Gamertag " & us.Name & " is not taken!"
      Else
        FlatAlertBox1.Text = "Gamertag " & us.Name & " is taken :("
      End If
      FlatAlertBox1.kind = us.Kind
      FlatAlertBox1.Visible = True
    End Sub
    

    A couple of notes about your code though. You are looping through a list but placing all your information in the same TextBox, so you are only ever seeing the last item in the list (unless the process is really slow).

    I removed the Try-Catch. If an error occurs in a BackgroundWorker, it will get reported in the RunWorkerCompleted event in the e.Error property. You should examine any errors that occurred there.