Seperate Thread progress form

I am trying to create a Progress form for my program that loads in a seperate thread so that screen updates and animations on the form are smooth and timely. I have had some success with the following code.

The Form Used as a "Template" for the threaded progress form:

Public Class frmDebug


Private m_Thread As Threading.Thread
Private m_CloseByCode As
Boolean

Public Shadows Sub AsyncShow()

m_Thread = New Threading.Thread(AddressOf ThreadMain)
m_Thread.Start()

End Sub

Private Sub ThreadMain()

UctlProgressSpinner1.Style = 1
UctlProgressSpinner1.Active =
True
Application.Run(Me)

End Sub

Public Shadows Sub AsyncClose()

If Me.InvokeRequired Then

Me.BeginInvoke(New MethodInvoker(AddressOf AsyncClose))

Else

m_CloseByCode = True
MyBase.Close()
m_CloseByCode = False

End If

End Sub

Private Sub txtStatus_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)

Application.DoEvents()

End Sub

Private Delegate Sub ShowProgressDelegate( ByVal myText As String)

Public Sub AsyncShowProgress(ByVal myText As String)

If Me.InvokeRequired Then

Me.BeginInvoke( New ShowProgressDelegate(AddressOf AsyncShowProgress), _ New Object() {myText} )

Else

If myText = "done" Then

UctlProgressSpinner1.Active = False
Button1.Visible = True

Else

txtStatus.AppendText(myText)

End If

End If

End Sub

Protected Overrides Sub OnClosing(ByVal e As System.ComponentModel.CancelEventArgs)

MyBase.OnClosing(e)
e.Cancel = Not m_CloseByCode

End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

Me.AsyncClose()

End Sub

End Class

The Code from my class that shows the above form

Public Sub CreateUser()

frmMyDebugThread = New frmDebug
frmMyDebugThread.AsyncShow()
AddHandler Me.Progress, AddressOf DebugProgress
RaiseEvent Progress("Starting User Creation" & vbNewLine)

End Sub

Public Sub DeleteUser()

frmMyDebugThread = New frmDebug
frmMyDebugThread.AsyncShow()
AddHandler Me.Progress, AddressOf DebugProgress
RaiseEvent Progress("Deleting User" & vbNewLine)

End Sub

The Code from a Module which contains the object declarations and event handler sub routine

Public frmMyDebugThread As New frmDebug

Public Sub DebugProgress(ByVal myText As String)

frmMyDebugThread.AsyncShowProgress(myText)

End Sub

Everything works great the first time the form appears when called from the CreateUser() method however if I then try to run the DeleteUser() Method I get the following error message:

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

This error occurs on the following line of code from frmDebug.

Application.Run(Me)

HELP! ME! If anyone has some thoughts it would be appreciated

Patrick



Answer this question

Seperate Thread progress form

  • elsonidoq

    You will need the use of an invoker to properly send the UI process to the correct UI thread.


    Create a class and name it something like ThreadSafeUpdater
    Define a property TextBox
    Define a property Value

    Define a WrokerMethodDelegate for a WorkerMethod

    Create a constructor that accepts TextBox and Value // optional, just makes thing easier
    Create a method1 then will call BeginInvoke (read more on Invoker) on the WorkerMethod
    Define WorkerMethod that will update the TextBox


    In your async process, everytime you will need to update the textbox. You define an instance of the above class and pass the textbox object and the new value to it. Then call method1().

    If you ever need some other parameter just add it as a property.



    BTW, Application.Run(Me) should this be Me.ShowDialog()

  • VijayG

    Hi Peter,

    Well I have had some success with the background worker but had to move some DIM statements out of my class and then pass them to the class from the form to get it to work. Hopefully this will have no adverse effects, only testing will tell. For those of you watching this thread this is what frmDebug now looks like.

    Public Class frmDebug

    Private Sub txtStatus_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)

    Application.DoEvents()

    End Sub

    Private Delegate Sub ShowProgressDelegate( ByVal myText As String)

    Public Sub ShowProgress(ByVal myText As String)

    If Me.InvokeRequired Then

    Me.BeginInvoke( New ShowProgressDelegate(AddressOf ShowProgress), New Object() {myText} )

    Else

    If myText = "done" Then

    UctlProgressSpinner1.Active = False
    Button1.Visible = True

    Else

    txtStatus.AppendText(myText)

    End If

    End If

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Me.Close()

    End Sub

    Private Sub frmDebug_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    UctlProgressSpinner1.Style = 1
    UctlProgressSpinner1.Active = True

    End Sub

    End Class

    The Button_Click event on my main form is as follows

    Private Sub cmdCreateRemoveAccount_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdCreateRemoveAccount.Click

    frmMyDebug = New frmDebug

    Select Case cmdCreateRemoveAccount.Text

    Case "Remove Account"

    bwTasksRemove.RunWorkerAsync()
    frmMyDebug.ShowDialog()

    Case "Create Account"

    bwTasksCreate.RunWorkerAsync()
    frmMyDebug.ShowDialog()

    End Select

    End Sub

    From the Backgroundworker dowork handler I do this

    Private Sub bwTasksCreate_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bwTasksCreate.DoWork

    myTasksUser.Create(frmMyDebug)

    End Sub

    My Class method is defined as follows and the frmDebug method so that you can update the txtStatus safely

    Public Sub Create(ByRef frmMyDebug As frmDebug)

    frmMyDebug.ShowProgress("Starting User Creation" & vbNewLine)

    end sub


  • Lewis Lynn

    hi,

    Is your problem solved

    Thank you,
    Bhanu.



  • just*Do*It

    Peter Ritchie wrote:
    The problem is, doing this sort of thing results in undefined behaviour. There's no way to tell specifically why it works or why it doesn't. Some of the variables this behaviour may depend upon: timing, memory allocation order, memory allocation size, order of operations leading up to this operation, etc. Change any of those variables (like run program x before y, instead of y before x; or run program z in the background, run it on Windows 2000 instead of Windows XP, etc.) and your scenario may not even work the first time. You will run into situations where it won't run the first time. They will be random and almost impossible to track down.

    Basically, it's a fluke that it works at all. And, with the randomization features in Vista, my guess is it will completely fail if you try to run it in Vista. It's like allocating x bytes of memory, initializing them to values you need, freeing the memory, then accessing the memory depending on those values still being there. Sometimes they'll still be there, sometimes they won't; it would depend on alot of things outside of your control.

    I'd like to give you a better outlook on this; but, Windows just doesn't support this sort of thing, although it lets you try and do it.

    Ok then I guess you could give me some help with doing it the right way, let me try to explain the app.

    1. A button on a form is clicked
    2. The button executes the .CreateUser() or .DeleteUser() methods of a class I have created which has an instance that was created in the form.load() event
    3. These methods have parameters both required and optional that are passed

    Can this class method be launched as a background worker process and if so do you have some sample code.

    Can this class method in a background worker thread access the frmDebug class , (with some changes) but I would suspect I would still have to use the AsyncShowProgress method to update the txtStatus on the form

    Some clean sample code would really help as I have tried some many different things that I think maybe confused as to what I need to do it the right way.


  • Serega

    pfitchie wrote:
    I am way to far in to change what I have done and since the code I have works, all be it only the first time, I really would like to find why it won't work the second time through.
    The problem is, doing this sort of thing results in undefined behaviour. There's no way to tell specifically why it works or why it doesn't. Some of the variables this behaviour may depend upon: timing, memory allocation order, memory allocation size, order of operations leading up to this operation, etc. Change any of those variables (like run program x before y, instead of y before x; or run program z in the background, run it on Windows 2000 instead of Windows XP, etc.) and your scenario may not even work the first time. You will run into situations where it won't run the first time. They will be random and almost impossible to track down.

    Basically, it's a fluke that it works at all. And, with the randomization features in Vista, my guess is it will completely fail if you try to run it in Vista. It's like allocating x bytes of memory, initializing them to values you need, freeing the memory, then accessing the memory depending on those values still being there. Sometimes they'll still be there, sometimes they won't; it would depend on alot of things outside of your control.

    I'd like to give you a better outlook on this; but, Windows just doesn't support this sort of thing, although it lets you try and do it.



  • SpoBo

    Peter Ritchie wrote:

    Windows only supports one GUI thread. A user can only deal with one form at a time, there's really no reason to put form on a background thread.

    You should create a background worker thread that communicates it's progress back to the form. This is best done with the BackgroundWorker class. If you use the Thread class you have to make sure changes to the form are done in the GUI thread, which can be check with Control.InvokeRequired. Usually, a setter method is provided to modify form data which checks the InvokeRequired property and calls BeginInvoke where needed.

    While I now understand alot more about Threading in Visual Basic and I understand the proper way of doing it now, I am way to far in to change what I have done and since the code I have works, all be it only the first time, I really would like to find why it won't work the second time through.


  • MShanahan

    Windows only supports one GUI thread. A user can only deal with one form at a time, there's really no reason to put form on a background thread.

    You should create a background worker thread that communicates it's progress back to the form. This is best done with the BackgroundWorker class. If you use the Thread class you have to make sure changes to the form are done in the GUI thread, which can be check with Control.InvokeRequired. Usually, a setter method is provided to modify form data which checks the InvokeRequired property and calls BeginInvoke where needed.



  • Seperate Thread progress form