new Thread versus BackgroundWorker updating UI

roccoman

Active member
Joined
Aug 24, 2010
Messages
36
Location
Indiana
Programming Experience
5-10
hi all, i have lurked on these forums before and finally signed up


i have a situation that, after days of searching the web, i have not been able to come up with a solution for.

i have to fire off a function that takes a large amount of time (writes to database, creates files, etc etc)

this function is a non-shared function of a class. if need be i could make it Shared.

since this is time consuming and locks up the UI, i am calling it via a delegate in a new thread.
VB.NET:
Public Class clsSendBatchDelegate

           Private log As clsMailLogWithEmails = Nothing
           Private WithEvents mm As clsMailManager = Nothing

           Public Sub SendBatchToQueue()

               ''  this is the routine that takes a LONG time
               mm.SendBatchToQueue(Me.log)

           End Sub

           Public Sub New(ByVal mmX As clsMailManager, ByVal logX As  clsMailLogWithEmails)
               Me.log = logX
               Me.mm = mmX
           End Sub
end class


this code is being called from another class's subroutine:
VB.NET:
           Dim arg As New Managers.clsMailManager.clsSendBatchDelegate(mm, log)
           thrd = New System.Threading.Thread(AddressOf arg.SendBatchToQueue)
           thrd.Start()

the code works great, but what i need to be able to do is update a form with a progress bar as the job is processing.

all of the examples i have seen that do this are being called from within a Windows Form and they access the form directly - i cant do that in this case. my form needs to be 1 form for 1 job being executed - can process multiple jobs at one time and thus i need multiple forms.

i have tried making the form part of my delegate class but since it runs in the same thread the effect is it "freezes" until the job is complete.

i have just started looking into using a background worker but again, all examples i have seen use the component as part of a form - that wont work for me.

any ideas? if anything is unclear please let me know!
 
you can create an instance of a background worker in code instead of as a component...

BackgroundWorker Class (System.ComponentModel)

THe example in there is all in code... note that BackgroundWorker is designed around the ComponentModel, meaning it's used in Windows and Forms and the sort. Hence why it's in the System.ComponentModel namespace.

Note the System.ComponentModel description:

from: System.ComponentModel Namespace ()
The System.ComponentModel namespace provides classes that are used to implement the run-time and design-time behavior of components and controls. This namespace includes the base classes and interfaces for implementing attributes and type converters, binding to data sources, and licensing components.


There is of course other ways to accomplish async calls and the sort. Here's the common design in VB. The "Begin" and "End" design.

I'll give you two completely programmatic methods of doing them (no forms are used at all here), we do a sqrt that sleeps the thread.


AsyncExample1 - uses the Begin/End IAsyncResult model... I prefer this method, but you may notice there's a lot more code:
VB.NET:
Imports System
Imports System.Threading

Public Class AsyncExample1

    Public Sub New()

        Init()

    End Sub

    Private Sub Init()

        Me.BeginDoSqrt(9, 5000, New AsyncCallback(AddressOf Me.DoSqrtCallback), "Thread One")
        Me.BeginDoSqrt(16, 1000, New AsyncCallback(AddressOf Me.DoSqrtCallback), "Thread Two")
        Me.BeginDoSqrt(25, 3000, New AsyncCallback(AddressOf Me.DoSqrtCallback), "Thread Three")

    End Sub

    Private Sub DoSqrtCallback(ByVal ar As IAsyncResult)
        Console.WriteLine(ar.AsyncState & " Finished With Result: " & Me.EndDoSqrt(ar))
    End Sub


#Region "DoSqrt"
    Public Function DoSqrt(ByVal param As Double, ByVal delay As Integer) As Double
        Thread.Sleep(delay)

        Return Math.Sqrt(param)
    End Function

    Public Function BeginDoSqrt(ByVal param As Double, ByVal delay As Integer, ByVal callBack As System.AsyncCallback, ByVal state As Object) As IAsyncResult
        Dim asyncRes As New DoSqrtAsyncResult(callBack, state)
        asyncRes.Value = param
        asyncRes.Delay = delay

        Threading.ThreadPool.QueueUserWorkItem(New Threading.WaitCallback(AddressOf Me.DoSqrtThreadout), asyncRes)

        Return asyncRes
    End Function

    Public Function EndDoSqrt(ByVal asyncResult As IAsyncResult) As Double
        Dim result As Double = 0

        If TypeOf asyncResult Is DoSqrtAsyncResult Then
            Dim ares As DoSqrtAsyncResult = DirectCast(asyncResult, DoSqrtAsyncResult)
            ares.AsyncWaitHandle.WaitOne()
            result = ares.Result
        End If

        Return result
    End Function

    Private Sub DoSqrtThreadout(ByVal asyncResult As Object)
        If TypeOf asyncResult Is DoSqrtAsyncResult Then

            Dim ares As DoSqrtAsyncResult = DirectCast(asyncResult, DoSqrtAsyncResult)
            ares.Result = DoSqrt(ares.Value, ares.Delay)

            ares.OnCompleted()
        End If
    End Sub
#End Region

End Class

Public Class AsyncResult : Implements IAsyncResult, IDisposable

    Private _AsyncCallback As AsyncCallback
    Private _State As Object
    Private _ManualResetEvent As ManualResetEvent


#Region "CONSTRUCTOR"
    ''' <summary>
    ''' Initializes a new instance of the <see cref="AsyncResult"/> class
    ''' </summary>
    ''' <param name="callBack">The callback</param>
    ''' <param name="state">The state</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal callBack As AsyncCallback, ByVal state As Object)
        _AsyncCallback = callBack
        _State = state
        _ManualResetEvent = New ManualResetEvent(False)
    End Sub
#End Region

    Public Overridable Sub OnCompleted()
        _ManualResetEvent.Set()
        If Not _AsyncCallback Is Nothing Then
            _AsyncCallback(Me)
        End If
    End Sub

#Region "IAsyncResult Interface"
    ''' <summary>
    ''' Gets a user-defined object that qualifies or contains information about an asynchronous operation.
    ''' </summary>
    ''' <value></value>
    ''' <returns>A user-defined object that qualifies or contains information about an asynchronous operation</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property AsyncState() As Object Implements System.IAsyncResult.AsyncState
        Get
            Return _State
        End Get
    End Property

    ''' <summary>
    ''' Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
    ''' </summary>
    ''' <value></value>
    ''' <returns>A <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.</returns>
    Public ReadOnly Property AsyncWaitHandle() As System.Threading.WaitHandle Implements System.IAsyncResult.AsyncWaitHandle
        Get
            Return _ManualResetEvent
        End Get
    End Property

    Public ReadOnly Property CompletedSynchronously() As Boolean Implements System.IAsyncResult.CompletedSynchronously
        Get
            Return False
        End Get
    End Property

    Public ReadOnly Property IsCompleted() As Boolean Implements System.IAsyncResult.IsCompleted
        Get
            Return _ManualResetEvent.WaitOne(0, False)
        End Get
    End Property
#End Region

#Region "IDisposable Interface"
    Private _bIsDisposed As Boolean = False        ' To detect redundant calls
    Private Event Disposed As System.EventHandler

    ''' <summary>
    ''' Gets a value indicating whether this instance is diposed.
    ''' </summary>
    ''' <value><c>true</c> if this instance is disposed; otherwise, <c>false</c>.</value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <System.ComponentModel.Browsable(False)> _
    Public ReadOnly Property IsDisposed() As Boolean
        Get
            Return _bIsDisposed
        End Get
    End Property

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        Try
            If Not Me._bIsDisposed Then
                If disposing Then
                    _ManualResetEvent.Close()
                    _ManualResetEvent = Nothing
                    _State = Nothing
                    _AsyncCallback = Nothing

                    RaiseEvent Disposed(Me, System.EventArgs.Empty)
                End If

                ' TODO: free your own state (unmanaged objects).
                ' TODO: set large fields to null.
            End If
        Catch ex As Exception

        Finally
            Me._bIsDisposed = True

        End Try
    End Sub

#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region
#End Region
End Class

Public Class DoSqrtAsyncResult : Inherits AsyncResult

    Private _value As Double
    Private _delay As Integer
    Private _result As Double

    Public Sub New(ByVal callBack As AsyncCallback, ByVal state As Object)
        MyBase.New(callBack, state)
    End Sub

    Public Property Value() As Double
        Get
            Return _value
        End Get
        Set(ByVal value As Double)
            _value = value
        End Set
    End Property

    Public Property Delay() As Integer
        Get
            Return _delay
        End Get
        Set(ByVal value As Integer)
            _delay = value
        End Set
    End Property

    Public Property Result() As Double
        Get
            Return _result
        End Get
        Set(ByVal value As Double)
            _result = value
        End Set
    End Property

End Class


AsyncExample2 - BackgroundWorker is shorter, BUT it comes with some draw backs... read the notes
VB.NET:
Imports System
Imports System.Threading
Imports System.ComponentModel

Public Class AsyncExample2

    Private WithEvents _bgworker As New BackgroundWorker()

    Public Sub New()

        Init()

    End Sub

    Private Sub Init()

        Me.AsyncDoSqrt(9, 5000, AddressOf Me.DoSqrtCallback, "Thread One")
        ''can't do this with background worker because the background worker only allows 1 concurrent job
        ''Me.AsyncDoSqrt(16, 1000, AddressOf Me.DoSqrtCallback, "Thread Two")
        ''Me.AsyncDoSqrt(25, 3000, AddressOf Me.DoSqrtCallback, "Thread Three")

    End Sub

    Private Sub DoSqrtCallback(ByVal result As Double, ByVal state As Object)
        Console.WriteLine(state & " Finished With Result: " & result)
    End Sub


#Region "DoSqrt"
    Public Function DoSqrt(ByVal param As Double, ByVal delay As Integer) As Double
        Thread.Sleep(delay)

        Return Math.Sqrt(param)
    End Function

    Public Sub AsyncDoSqrt(ByVal param As Double, ByVal delay As Integer, ByVal callBack As Action(Of Double, Object), ByVal state As Object)
        ''big reason I don't like this method... not type safe at all
        _bgworker.RunWorkerAsync(New Object() {param, delay, callBack, state})

    End Sub

    Private Sub AsyncDoSqrt_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles _bgworker.DoWork
        ''big reason I don't like this method... not type safe at all
        Dim args As Object() = CType(e.Argument, Object())
        Dim value As Double = CDbl(args(0))
        Dim delay As Integer = CInt(args(1))
        Dim callback As Action(Of Double, Object) = CType(args(2), Action(Of Double, Object))
        Dim state As Object = args(3)

        e.Result = New Object() {DoSqrt(value, delay), callback, state}
    End Sub

    Private Sub AsyncDoSqrt_Complete(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles _bgworker.RunWorkerCompleted
        ''big reason I don't like this method... not type safe at all
        Dim args As Object() = CType(e.Result, Object())
        Dim result As Double = CDbl(args(0))
        Dim callback As Action(Of Double, Object) = CType(args(1), Action(Of Double, Object))
        Dim state As Object = args(2)

        callback(result, state)

    End Sub
#End Region



End Class

Of course there are other ways... such as using threads raw out there... which can be even shorter code, but can cause issues if the threads don't terminate or just aren't managed in anyway.
 
BackgroundWorker is very convenient and easy to use in many cases, so I recommend you take two and learn that also, but this can also be solved differently. To make calls operate in UI thread you need Control.Invoke (or BeginInvoke). Control is a reference to any control/form in UI thread, and to Invoke method you pass a delegate pointing to the method you want to call. Some .Net classes have a SynchronizingObject property of type ISynchronizeInvoke for this purpose, something you can add to your class also. Then you pass the UI instance and use this as sync object for Invoke calls.
 
Back
Top