Doubt on implementing Multithreading

VBobCat

Well-known member
Joined
Sep 6, 2011
Messages
137
Location
S?o Paulo, Brazil
Programming Experience
3-5
Hello people,

I'll explain this theoretically because I feel I need some conceptual guidance before coding.
My application manages bureaucratic flow of paper documents, in order to distribute them to the due personal which has to deal with them.
The main user has a huge pile of papers, and he does his duty by entering from each paper its serial number, and the names and/or ID numbers of people involved and mentioned in it.
The system registers the paper and distributes it to one member of the staff responsible to give the paper further analysis, using an algorythm that provides random and balanced distribution, in order every member gets moreover the same amount of work at the end of the day.
Some of the work of these people, then, demands internet researching for collection of data related to people mentioned in those papers content.
This resarch is done simply filling the ID number of those people into a series of websites, in order to get additional information about them, and then collect and register this information, and them perform some decision considering all that.
So these people came to me and asked me to include in my application an antecipated and automated research of that data, which I am actually able to do, using WebBrowser automation.
But there's a dilemma in it. Each web query will take some time, let's say 5 to 30 seconds to complete, and it depends on the speed of external websites, that can be rather fast one day, and annoyingly slow another.
In the other hand, I cannot insert this extra waiting time between each time the responsible user types the number and basic data of each paper, because if I did, what now takes half an hour would take four hours, and this user actually has other pieces of work to do besides feeding data into my system.
So I thought I could do this:
  • Each time user registers one paper, my application would invoke a routine to run once for each website that needs to be queried;
  • This routine, I think, should be a class, and this class:
    • Must be created out of the UI thread, preventing it to halt;
    • Has a constructor which takes a number (this number is the key for further querying) as argument;
    • Creates its own WebBrowser object to do its bidding;
    • Handles the DocumentCompleted event of this WebBrowser and collects the data it returns;
    • Writes this data into the database;
    • Signals when finished.
  • As the user continues to register papers, new classes are created out of the UI thread, even if the previous ones didn't finish their job;
  • All the routine inside UI thread has to do is monitor if there are active classes still working, in order to prevent user to close application before all of them are finished.
All that said, I have great doubt on how to implement this.
I was thinking I should have a single frame class that does the interface between the routine that is entangled with the UI, and the multiple query classes would be seen only by this frame class.
So I would first create it, by doing this:
VB.NET:
Dim MyParallelQueryThread As New System.Threading.Thread(AddressOf MyParallelQueryFrameClass.Prepare)
MyParallelQueryThread.Start
Each time a new paper is registered, the form summons that frame class to do the query:
VB.NET:
Dim PersonID as Long = Long.Parse(TextBoxPersonID.Text)
Invoke(Addressof MyParallelQueryFrameClass.QueryPerson, PersonID)
So this class would be something like this (this is just a stub):
Friend Class MyParallelQueryFrameClass
    Shared Queries As List(Of WebQuery)
    Shared Sub Prepare()
        Queries = New List(Of WebQuery)
    End Sub
    Shared Sub QueryPerson(PersonID As Long)
        Queries.Add(New WebQuery("Person", PersonID))
    End Sub
    Shared ReadOnly Property AllFinished As Boolean
        Get
            Return (From WQ As WebQuery In Queries Where WQ.Active Select WQ).Count = 0
        End Get
    End Property

    Class WebQuery
        Friend Active As Boolean
        Private WithEvents WB As WebBrowser
        Sub New(QueryType As String, QueryArgument As Object)
            Active = True
            If QueryType = "Person" Then
                WB.Navigate("http://www.personqueryuponid.sample.br")
            End If
        End Sub
        Private Sub WB_DC(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WB.DocumentCompleted
            'read html document

            'if it is query form:
            ' - fill the proper fields and submit;
            'end if

            'if it is query response:
            '- collect and write info into database
            Active = False
            'end if
        End Sub
    End Class
End Class


But then, again, after writing this stub, IntelliSense put the code below in curly-blue-underline:
Invoke(AddressOf MyParallelQueryFrameClass.QueryPerson, PersonID)
and said:
'AddressOf' expression cannot be converted to System.Delegate because type 'System.Delegate' is declared 'MustInherit' and cannot be created.

At this point, I'm running short of knowledge. I'd be grateful if anyone could put this plain and straight for me to understand it.
And of course, I'm already grateful for the patience of those who managed to read it all until here.
 
Okay, I've tried a different approach:
Public Class Form1
    Dim WebSearchList As New List(Of WebSearch)

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Dim MyThread As New System.Threading.Thread(AddressOf StartMyThread)
        MyThread.SetApartmentState(Threading.ApartmentState.STA)
        MyThread.Start()
        CheckStatus()
    End Sub
    Private Sub StartMyThread()
        WebSearchList.Add(New WebSearch(TextBox1.Text))
    End Sub
    Private Sub CheckStatus()
        Dim total = WebSearchList.Count
        Dim ended = (From ws As WebSearch In WebSearchList Where ws.Done Select ws).Count
        TextBox2.Text = String.Format("{0} searches started, {1} are done.", total, ended)
    End Sub
End Class

Public Class WebSearch
    Private _done As Boolean
    ReadOnly Property Done As Boolean
        Get
            Return _done
        End Get
    End Property
    Private _resultList As List(Of String)
    ReadOnly Property Results As String()
        Get
            Return _resultList.ToArray
        End Get
    End Property
    Private _wbr As WebBrowser
    Private _expression As String
    Sub New(ByVal expression As String)
        _wbr = New WebBrowser With {.ScriptErrorsSuppressed = True}
        _resultList = New List(Of String)
        _expression = expression
        _done = False
        AddHandler _wbr.DocumentCompleted, AddressOf GoogleStartHandle
        _wbr.Navigate("http://www.google.com.br/")
    End Sub
    Sub GoogleStartHandle(sender As Object, e As WebBrowserDocumentCompletedEventArgs)
        If _wbr.Url.ToString = "http://www.google.com.br/" Then
            RemoveHandler _wbr.DocumentCompleted, AddressOf GoogleStartHandle
            AddHandler _wbr.DocumentCompleted, AddressOf GoogleResultsHandle
            _wbr.Document.GetElementsByTagName("input")("q").SetAttribute("value", _expression)
            _wbr.Document.GetElementsByTagName("input")("btnK").InvokeMember("click")
        End If
    End Sub
    Sub GoogleResultsHandle(sender As Object, e As WebBrowserDocumentCompletedEventArgs)
        If _wbr.Url.ToString.StartsWith("http://www.google.com.br/search") Then
            _resultList.Add(_wbr.Document.Body.OuterText)
            RemoveHandler _wbr.DocumentCompleted, AddressOf GoogleResultsHandle
            AddHandler _wbr.DocumentCompleted, AddressOf BingStartHandle
            _wbr.Navigate("http://br.bing.com/")
        End If
    End Sub
    Sub BingStartHandle(sender As Object, e As WebBrowserDocumentCompletedEventArgs)
        If _wbr.Url.ToString = "http://br.bing.com/" Then
            RemoveHandler _wbr.DocumentCompleted, AddressOf BingStartHandle
            AddHandler _wbr.DocumentCompleted, AddressOf BingResultsHandle
            _wbr.Document.GetElementsByTagName("input")("q").SetAttribute("value", _expression)
            _wbr.Document.GetElementsByTagName("input")("go").InvokeMember("click")
        End If
    End Sub
    Sub BingResultsHandle(sender As Object, e As WebBrowserDocumentCompletedEventArgs)
        If _wbr.Url.ToString.StartsWith("http://br.bing.com/search") Then
            _resultList.Add(_wbr.Document.Body.OuterText)
            RemoveHandler _wbr.DocumentCompleted, AddressOf BingResultsHandle
            AddHandler _wbr.DocumentCompleted, AddressOf YahooStartHandle
            _wbr.Navigate("http://br.yahoo.com/")
        End If
    End Sub
    Sub YahooStartHandle(sender As Object, e As WebBrowserDocumentCompletedEventArgs)
        If _wbr.Url.ToString = "http://br.yahoo.com/" Then
            RemoveHandler _wbr.DocumentCompleted, AddressOf YahooStartHandle
            AddHandler _wbr.DocumentCompleted, AddressOf YahooResultsHandle
            _wbr.Document.GetElementsByTagName("input")("p").SetAttribute("value", _expression)
            _wbr.Document.GetElementsByTagName("button")("search-submit").InvokeMember("click")
        End If
    End Sub
    Sub YahooResultsHandle(sender As Object, e As WebBrowserDocumentCompletedEventArgs)
        If _wbr.Url.ToString.StartsWith("http://br.search.yahoo.com/search") Then
            _resultList.Add(_wbr.Document.Body.OuterText)
            RemoveHandler _wbr.DocumentCompleted, AddressOf YahooResultsHandle
            _done = True
            _wbr.Navigate("about:blank")
        End If
    End Sub
End Class


But now my searches never finish. After each pressing of the button, TextBox2 will state that one more search started, but it always states that zero are done.
I'd appreciate any help... Thanks.
 
I gave up. I put a timer to periodically check for pending queries and then call the executing routines from timer-elapsed handler sub. It's the best I'm able to do.
 
Back
Top