Thread safe invoke on a listbox woes

498 Views Asked by At

First post here. Long time lurker though. I'll get right to it.

My little side project here is an application that will scrape postings off of craigslist. Once scraped, the listing data gets sent to a listbox in "Form1". I have created a worker class to handle all the scraping. The problem arises when my class function "guiAdd()" will not properly populate the listbox.

Given Form1 does exists, and it does have a listbox control named "lvsearch":

This is where I perceive to be the problem: Class subroutine

Private Sub guiAdd(ByVal data As String)
    If Form1.lvsearch.InvokeRequired Then
        Form1.lvsearch.Invoke(New Action(Of String)(AddressOf guiAdd), data)
    Else
        Dim fitem As New ListViewItem
        fitem.Text = data
        'Form1.lvsearch.Items.Add(fitem) <---Original Version
        Form1.lvsearch.Items.Add(New ListViewItem("WTF!!!!")) '<--- Sanity Version
    End If
End Sub

Here is the full Class:

Imports System.Threading
Imports System.Threading.Thread
Imports System.IO
Imports System.Net

Public Class craigsearcher
    Private cURL As String 'Class scope variables preceded by "c"
    Private cSER As Integer





Public Sub New(ByVal fURL As String, Optional ByVal autoStart As Boolean = True)
    Try
        cURL = fURL 'Function scope variables preceded by "f"
        serialGen()
        If autoStart = True Then
            invokeSearch()
        End If
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try
End Sub





Private Sub invokeSearch()
    Dim cTHREAD As New Threading.Thread(AddressOf search)
    cTHREAD.IsBackground = True
    cTHREAD.Start()
End Sub





Private Sub serialGen()
    'Random number to track this threads temp files

    Dim fRND As Double
    Dim fINT As Integer

    Randomize()
    fRND = Rnd() * 1000000000
    fINT = Math.Floor(fRND)
    cSER = fINT.ToString
End Sub






Private Sub search()
    'Class Workflow

    prepData()
    extractData()
    'MessageBox.Show("Debug: End of Thread")
End Sub





Private Sub prepData()
    'WIP: This is just proof of concept currently.
    'Needs revision, but get the job done for now.

    Dim client As New WebClient()
    Dim rawHTML As String = client.DownloadString(New Uri(cURL))


    Dim fo As New StreamWriter(".\temp\" & cSER & ".dat")
    fo.WriteLine(rawHTML)
    fo.Close()
    Thread.Sleep(25)
    Dim fo2 As New StreamReader(".\temp\" & cSER & ".dat")
    Dim fo2str As String = ""
    Do Until fo2.EndOfStream = True
        fo2str = fo2str & Trim(fo2.ReadLine()) & vbCrLf
    Loop
    fo2.Close()
    Thread.Sleep(25)

    System.IO.File.Delete(".\temp\" & cSER & ".dat")

    Dim fo3 As New StreamWriter(".\temp\Prep" & cSER & ".dat")
    fo3.WriteLine(fo2str)
    fo3.Close()
    Thread.Sleep(25)
End Sub





Private Sub extractData()
    'WIP: This is just proof of concept currently.

    Dim fo As New StreamReader(".\temp\Prep" & cSER & ".dat")
    Dim fstr As String = ""

    Do Until fo.EndOfStream = True
        fstr = fo.ReadLine()

        If InStr(fstr, "<p class=") Then 
            'FUTURE LOGIC AND STUFFS SORTED HERE. Regex etc.
            guiAdd(fstr)
        End If

    Loop
End Sub





Public Sub guiAdd(ByVal data As String)
    If Form1.lvsearch.InvokeRequired Then
        Form1.lvsearch.Invoke(New Action(Of String)(AddressOf guiAdd), data)
    Else
        Dim fitem As New ListViewItem
        fitem.Text = data
        'Form1.lvsearch.Items.Add(fitem) <---Original Version
        Form1.lvsearch.Items.Add(New ListViewItem("WTF!!!!")) '<--- Sanity Version
    End If
End Sub

End Class

This is how the class gets instantiated:

 Dim worker As New craigsearcher("http://atlanta.craigslist.org/ggg/", True)

So, that is everything I have. I realize the parsing code needs revision / completion. I'll get to it. This is in a proof of concept stage. The proper data types get passed around. I just need some help understanding why addGUI() does not function properly. I am not particularly well versed in the world of multi-threading, so I am glad I made it this far.

I have been using this as a guide: http://www.vbforums.com/showthread.php?682082-Understanding-Multi-Threading-in-VB-Net

2

There are 2 best solutions below

3
On

Try revising this method:

Private Sub search()
    'Class Workflow

    prepData()
    extractData()
    'MessageBox.Show("Debug: End of Thread")
End Sub

to this

Private Sub search()
    'Class Workflow

    Thread.QueueUserWorkItem(prepData)
    Thread.QueueUserWorkItem(extractData)
    'MessageBox.Show("Debug: End of Thread")
End Sub

Try putting it on a Thread Pool. This means let the first method finishes first before the second one. It should be chronologically if you put it on a threading. This is maybe because the extractData() method has been finished first its processing rather than prepData() method.

Hope it helps.

1
On

have you tested this without a separate worker/thread?

this should be in your form, not your worker class/thread:

if you want, you can put it in the listview control, just make sure you can reference that from your other class.

Private Delegate Sub invokeGuiAdd(ByVal data As String)
Public Sub guiAdd(ByVal data As String)
    If InvokeRequired Then
        'Form1.lvsearch.Invoke(New Action(Of String)(AddressOf guiAdd), data)
        Invoke(New invokeGuiAdd(AddressOf guiAdd), New Object() {data})
    Else
        Dim fitem As New ListViewItem
        fitem.Text = data
        'Form1.lvsearch.Items.Add(fitem) <---Original Version
        lvsearch.Items.Add(New ListViewItem("WTF!!!!")) '<--- Sanity Version
    End If
End Sub

and

Form1.guiAdd(fstr)

oh, as a side-note, events are delegates, so you can use an event instead.