Home All Groups Group Topic Archive Search About
Author
20 Nov 2006 4:54 AM
Don
How to stop a process which is running in a separate thread!!!

I've got a class which performs some lengthy process in a background
(separate) thread. And this lengthy process raises events regularly to inform
the main thread of the progress (which is then displayed to the user).
Since the event is raised from another thread, i've used me.Invoke() to
update the ui properly.

However, my problem is in cancelling the process.
I need to be able to execute a method such as Cancel() which will return
only when the process has been cancelled.
Below is the code i've implemented.

How ever, the WaitOne() method will ALWAYS return FALSE.
I'm assuming this is becuase the WaitOne causes the current thread to wait
untill it receives a signal. & since the current thread is also in the
process of updating the UI, the background process thread is not able to send
the signal.
Hence a dead lock (if not for the timeout in the WaitOne())

I'm sure this HAS to be possible.
I need a mechanism to cancel the background process and WAIT till it
confirms that the process has been cancelled (i.e. wait till the process
notifies the main thread that the process has been cancelled).

How can this be done?

Below is a sample the code which outlies the above issue:


Public Class frmThreads
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer. 
    'Do not modify it using the code editor.
    Friend WithEvents Button1 As System.Windows.Forms.Button
    Friend WithEvents Button2 As System.Windows.Forms.Button
    Friend WithEvents Label1 As System.Windows.Forms.Label
    <System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
        Me.Button1 = New System.Windows.Forms.Button
        Me.Button2 = New System.Windows.Forms.Button
        Me.Label1 = New System.Windows.Forms.Label
        Me.SuspendLayout()
        '
        'Button1
        '
        Me.Button1.Location = New System.Drawing.Point(40, 120)
        Me.Button1.Name = "Button1"
        Me.Button1.TabIndex = 0
        Me.Button1.Text = "Button1"
        '
        'Button2
        '
        Me.Button2.Location = New System.Drawing.Point(128, 120)
        Me.Button2.Name = "Button2"
        Me.Button2.TabIndex = 1
        Me.Button2.Text = "Button2"
        '
        'Label1
        '
        Me.Label1.Location = New System.Drawing.Point(32, 24)
        Me.Label1.Name = "Label1"
        Me.Label1.TabIndex = 2
        Me.Label1.Text = "Label1"
        '
        'frmThreads
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(292, 266)
        Me.Controls.Add(Me.Label1)
        Me.Controls.Add(Me.Button2)
        Me.Controls.Add(Me.Button1)
        Me.Name = "frmThreads"
        Me.Text = "frmThreads"
        Me.ResumeLayout(False)

    End Sub

#End Region

    Private _lengthyProcess As LengthyProcess
    Private _counter As Integer
    Private dlgIvokeProgress As New dlgProgress(AddressOf ShowProgress)

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
        _lengthyProcess = New LengthyProcess
        AddHandler _lengthyProcess.Progress, AddressOf LengthyProcess_Progress

        Dim start As New Threading.Thread(AddressOf StartProcess)
        start.Start()
    End Sub

    Private Sub StartProcess()
        _lengthyProcess.Start()
    End Sub

    Private Sub LengthyProcess_Progress(ByVal counter As Integer)
        _counter = counter
        Me.Invoke(dlgIvokeProgress)
    End Sub

    Private Delegate Sub dlgProgress()
    Private Sub ShowProgress()
        Me.Label1.Text = _counter
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
        MsgBox(_lengthyProcess.Cancel)
    End Sub
End Class

Public Class LengthyProcess

    Public Event Progress(ByVal counter As Integer)

    Private _counter As Integer
    Private _cancelled As Boolean
    Private _stopped As Threading.ManualResetEvent

    Public Sub Start()
        Do While _cancelled = False
            _counter += 1

            Threading.Thread.Sleep(100)
            RaiseEvent Progress(_counter)
        Loop

        If _cancelled = True Then
            _stopped.Set()
        End If
    End Sub

    Public Function Cancel() As Boolean
        _stopped = New Threading.ManualResetEvent(False)
        _cancelled = True


        'this will ALWAYS return FALSE
        'how ever if i were to display a messagebox here and THEN
        'execute the WaitOne(), it will return TRUE
        'MsgBox("WaitOne will now return TRUE")

        Return _stopped.WaitOne(5000, False)
    End Function
End Class

Author
20 Nov 2006 6:27 AM
Stephany Young
I think that you've tried to over-engineer it a tad and in the process have
confused yourself. Try this very simple code and then take it from there:

Note that the thread is now completely encapsulated within the
LengthyProcess class and the calling process does not need to know anything
about the thread. It only needs to know that the LengthyProcess class has a
Start method, a Cancel method and raises a Progress event.

In the LengthyProcess.Cancel method the call to _process.Join() has the
effect of waiting until the thread has actually finished which should be
somewhere up to 100 milliseconds plus the overhead for raising the Progress
event after the _cancel variable is set.

Also note that your UI thread (the form) will block each time the Progress
event is raised for as long at it takes to execute the ShowProgress method.
This means that ShowProgress must be as lean and mean as you can make it. As
an alternatively you could use BeginInvoke instead of Invoke which has the
effect of sending the ShowProgress method off to do it's stuff and
immediately allowing the LengthyProcess thread to continue.

<snip>all the preamble</snip

  Private _lengthyProcess As LengthyProcess
  Private _counter As Integer
  Private Delegate Sub dlgProgress()
  Private dlgIvokeProgress As New dlgProgress(AddressOf ShowProgress)

  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
    _lengthyProcess = New LengthyProcess
    AddHandler _lengthyProcess.Progress, AddressOf LengthyProcess_Progress
    _lengthyProcess.Start()
  End Sub

  Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click
    _lengthyProcess.Cancel
  End Sub

  Private Sub LengthyProcess_Progress(ByVal counter As Integer)
    _counter = counter
    Invoke(dlgIvokeProgress)
  End Sub

  Private Sub ShowProgress()
    Label1.Text = _counter.ToString()
    Label1.Update()
  End Sub

End Class

Public Class LengthyProcess

  Public Event Progress(ByVal counter As Integer)
  Private _counter As Integer = 0
  Private _process as Thread = Nothing
  Private _cancel as Boolean = False

  Public Sub Start()
    _process = New Thread(AddressOf TheProcess)
    _process.Start()
  End Sub

  Public Sub Cancel()
    _cancel = True
    _process.Join()
  End Sub

  Private Sub TheProcess()
    While Not _cancel
      _counter += 1
      Thread.Sleep(100)
      RaiseEvent Progress(_counter)
    End While
  End Sub

End Class


Show quoteHide quote
"Don" <D**@discussions.microsoft.com> wrote in message
news:B71E1C98-EB63-486E-AA86-1DF9DD0123A9@microsoft.com...
> How to stop a process which is running in a separate thread!!!
>
> I've got a class which performs some lengthy process in a background
> (separate) thread. And this lengthy process raises events regularly to
> inform
> the main thread of the progress (which is then displayed to the user).
> Since the event is raised from another thread, i've used me.Invoke() to
> update the ui properly.
>
> However, my problem is in cancelling the process.
> I need to be able to execute a method such as Cancel() which will return
> only when the process has been cancelled.
> Below is the code i've implemented.
>
> How ever, the WaitOne() method will ALWAYS return FALSE.
> I'm assuming this is becuase the WaitOne causes the current thread to wait
> untill it receives a signal. & since the current thread is also in the
> process of updating the UI, the background process thread is not able to
> send
> the signal.
> Hence a dead lock (if not for the timeout in the WaitOne())
>
> I'm sure this HAS to be possible.
> I need a mechanism to cancel the background process and WAIT till it
> confirms that the process has been cancelled (i.e. wait till the process
> notifies the main thread that the process has been cancelled).
>
> How can this be done?
>
> Below is a sample the code which outlies the above issue:
>
>
> Public Class frmThreads
>    Inherits System.Windows.Forms.Form
>
> #Region " Windows Form Designer generated code "
>
>    Public Sub New()
>        MyBase.New()
>
>        'This call is required by the Windows Form Designer.
>        InitializeComponent()
>
>        'Add any initialization after the InitializeComponent() call
>
>    End Sub
>
>    'Form overrides dispose to clean up the component list.
>    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
>        If disposing Then
>            If Not (components Is Nothing) Then
>                components.Dispose()
>            End If
>        End If
>        MyBase.Dispose(disposing)
>    End Sub
>
>    'Required by the Windows Form Designer
>    Private components As System.ComponentModel.IContainer
>
>    'NOTE: The following procedure is required by the Windows Form Designer
>    'It can be modified using the Windows Form Designer.
>    'Do not modify it using the code editor.
>    Friend WithEvents Button1 As System.Windows.Forms.Button
>    Friend WithEvents Button2 As System.Windows.Forms.Button
>    Friend WithEvents Label1 As System.Windows.Forms.Label
>    <System.Diagnostics.DebuggerStepThrough()> Private Sub
> InitializeComponent()
>        Me.Button1 = New System.Windows.Forms.Button
>        Me.Button2 = New System.Windows.Forms.Button
>        Me.Label1 = New System.Windows.Forms.Label
>        Me.SuspendLayout()
>        '
>        'Button1
>        '
>        Me.Button1.Location = New System.Drawing.Point(40, 120)
>        Me.Button1.Name = "Button1"
>        Me.Button1.TabIndex = 0
>        Me.Button1.Text = "Button1"
>        '
>        'Button2
>        '
>        Me.Button2.Location = New System.Drawing.Point(128, 120)
>        Me.Button2.Name = "Button2"
>        Me.Button2.TabIndex = 1
>        Me.Button2.Text = "Button2"
>        '
>        'Label1
>        '
>        Me.Label1.Location = New System.Drawing.Point(32, 24)
>        Me.Label1.Name = "Label1"
>        Me.Label1.TabIndex = 2
>        Me.Label1.Text = "Label1"
>        '
>        'frmThreads
>        '
>        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
>        Me.ClientSize = New System.Drawing.Size(292, 266)
>        Me.Controls.Add(Me.Label1)
>        Me.Controls.Add(Me.Button2)
>        Me.Controls.Add(Me.Button1)
>        Me.Name = "frmThreads"
>        Me.Text = "frmThreads"
>        Me.ResumeLayout(False)
>
>    End Sub
>
> #End Region
>
>    Private _lengthyProcess As LengthyProcess
>    Private _counter As Integer
>    Private dlgIvokeProgress As New dlgProgress(AddressOf ShowProgress)
>
>    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
> System.EventArgs) Handles Button1.Click
>        _lengthyProcess = New LengthyProcess
>        AddHandler _lengthyProcess.Progress, AddressOf
> LengthyProcess_Progress
>
>        Dim start As New Threading.Thread(AddressOf StartProcess)
>        start.Start()
>    End Sub
>
>    Private Sub StartProcess()
>        _lengthyProcess.Start()
>    End Sub
>
>    Private Sub LengthyProcess_Progress(ByVal counter As Integer)
>        _counter = counter
>        Me.Invoke(dlgIvokeProgress)
>    End Sub
>
>    Private Delegate Sub dlgProgress()
>    Private Sub ShowProgress()
>        Me.Label1.Text = _counter
>    End Sub
>
>    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
> System.EventArgs) Handles Button2.Click
>        MsgBox(_lengthyProcess.Cancel)
>    End Sub
> End Class
>
> Public Class LengthyProcess
>
>    Public Event Progress(ByVal counter As Integer)
>
>    Private _counter As Integer
>    Private _cancelled As Boolean
>    Private _stopped As Threading.ManualResetEvent
>
>    Public Sub Start()
>        Do While _cancelled = False
>            _counter += 1
>
>            Threading.Thread.Sleep(100)
>            RaiseEvent Progress(_counter)
>        Loop
>
>        If _cancelled = True Then
>            _stopped.Set()
>        End If
>    End Sub
>
>    Public Function Cancel() As Boolean
>        _stopped = New Threading.ManualResetEvent(False)
>        _cancelled = True
>
>
>        'this will ALWAYS return FALSE
>        'how ever if i were to display a messagebox here and THEN
>        'execute the WaitOne(), it will return TRUE
>        'MsgBox("WaitOne will now return TRUE")
>
>        Return _stopped.WaitOne(5000, False)
>    End Function
> End Class
Author
20 Nov 2006 10:16 PM
Don
Using BeginInvoke() did the trick
Thanks.


Show quoteHide quote
"Stephany Young" wrote:

> I think that you've tried to over-engineer it a tad and in the process have
> confused yourself. Try this very simple code and then take it from there:
>
> Note that the thread is now completely encapsulated within the
> LengthyProcess class and the calling process does not need to know anything
> about the thread. It only needs to know that the LengthyProcess class has a
> Start method, a Cancel method and raises a Progress event.
>
> In the LengthyProcess.Cancel method the call to _process.Join() has the
> effect of waiting until the thread has actually finished which should be
> somewhere up to 100 milliseconds plus the overhead for raising the Progress
> event after the _cancel variable is set.
>
> Also note that your UI thread (the form) will block each time the Progress
> event is raised for as long at it takes to execute the ShowProgress method.
> This means that ShowProgress must be as lean and mean as you can make it. As
> an alternatively you could use BeginInvoke instead of Invoke which has the
> effect of sending the ShowProgress method off to do it's stuff and
> immediately allowing the LengthyProcess thread to continue.
>
> <snip>all the preamble</snip
>
>   Private _lengthyProcess As LengthyProcess
>   Private _counter As Integer
>   Private Delegate Sub dlgProgress()
>   Private dlgIvokeProgress As New dlgProgress(AddressOf ShowProgress)
>
>   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
> System.EventArgs) Handles Button1.Click
>     _lengthyProcess = New LengthyProcess
>     AddHandler _lengthyProcess.Progress, AddressOf LengthyProcess_Progress
>     _lengthyProcess.Start()
>   End Sub
>
>   Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
> System.EventArgs) Handles Button2.Click
>     _lengthyProcess.Cancel
>   End Sub
>
>   Private Sub LengthyProcess_Progress(ByVal counter As Integer)
>     _counter = counter
>     Invoke(dlgIvokeProgress)
>   End Sub
>
>   Private Sub ShowProgress()
>     Label1.Text = _counter.ToString()
>     Label1.Update()
>   End Sub
>
> End Class
>
> Public Class LengthyProcess
>
>   Public Event Progress(ByVal counter As Integer)
>   Private _counter As Integer = 0
>   Private _process as Thread = Nothing
>   Private _cancel as Boolean = False
>
>   Public Sub Start()
>     _process = New Thread(AddressOf TheProcess)
>     _process.Start()
>   End Sub
>
>   Public Sub Cancel()
>     _cancel = True
>     _process.Join()
>   End Sub
>
>   Private Sub TheProcess()
>     While Not _cancel
>       _counter += 1
>       Thread.Sleep(100)
>       RaiseEvent Progress(_counter)
>     End While
>   End Sub
>
> End Class
>
>
> "Don" <D**@discussions.microsoft.com> wrote in message
> news:B71E1C98-EB63-486E-AA86-1DF9DD0123A9@microsoft.com...
> > How to stop a process which is running in a separate thread!!!
> >
> > I've got a class which performs some lengthy process in a background
> > (separate) thread. And this lengthy process raises events regularly to
> > inform
> > the main thread of the progress (which is then displayed to the user).
> > Since the event is raised from another thread, i've used me.Invoke() to
> > update the ui properly.
> >
> > However, my problem is in cancelling the process.
> > I need to be able to execute a method such as Cancel() which will return
> > only when the process has been cancelled.
> > Below is the code i've implemented.
> >
> > How ever, the WaitOne() method will ALWAYS return FALSE.
> > I'm assuming this is becuase the WaitOne causes the current thread to wait
> > untill it receives a signal. & since the current thread is also in the
> > process of updating the UI, the background process thread is not able to
> > send
> > the signal.
> > Hence a dead lock (if not for the timeout in the WaitOne())
> >
> > I'm sure this HAS to be possible.
> > I need a mechanism to cancel the background process and WAIT till it
> > confirms that the process has been cancelled (i.e. wait till the process
> > notifies the main thread that the process has been cancelled).
> >
> > How can this be done?
> >
> > Below is a sample the code which outlies the above issue:
> >
> >
> > Public Class frmThreads
> >    Inherits System.Windows.Forms.Form
> >
> > #Region " Windows Form Designer generated code "
> >
> >    Public Sub New()
> >        MyBase.New()
> >
> >        'This call is required by the Windows Form Designer.
> >        InitializeComponent()
> >
> >        'Add any initialization after the InitializeComponent() call
> >
> >    End Sub
> >
> >    'Form overrides dispose to clean up the component list.
> >    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
> >        If disposing Then
> >            If Not (components Is Nothing) Then
> >                components.Dispose()
> >            End If
> >        End If
> >        MyBase.Dispose(disposing)
> >    End Sub
> >
> >    'Required by the Windows Form Designer
> >    Private components As System.ComponentModel.IContainer
> >
> >    'NOTE: The following procedure is required by the Windows Form Designer
> >    'It can be modified using the Windows Form Designer.
> >    'Do not modify it using the code editor.
> >    Friend WithEvents Button1 As System.Windows.Forms.Button
> >    Friend WithEvents Button2 As System.Windows.Forms.Button
> >    Friend WithEvents Label1 As System.Windows.Forms.Label
> >    <System.Diagnostics.DebuggerStepThrough()> Private Sub
> > InitializeComponent()
> >        Me.Button1 = New System.Windows.Forms.Button
> >        Me.Button2 = New System.Windows.Forms.Button
> >        Me.Label1 = New System.Windows.Forms.Label
> >        Me.SuspendLayout()
> >        '
> >        'Button1
> >        '
> >        Me.Button1.Location = New System.Drawing.Point(40, 120)
> >        Me.Button1.Name = "Button1"
> >        Me.Button1.TabIndex = 0
> >        Me.Button1.Text = "Button1"
> >        '
> >        'Button2
> >        '
> >        Me.Button2.Location = New System.Drawing.Point(128, 120)
> >        Me.Button2.Name = "Button2"
> >        Me.Button2.TabIndex = 1
> >        Me.Button2.Text = "Button2"
> >        '
> >        'Label1
> >        '
> >        Me.Label1.Location = New System.Drawing.Point(32, 24)
> >        Me.Label1.Name = "Label1"
> >        Me.Label1.TabIndex = 2
> >        Me.Label1.Text = "Label1"
> >        '
> >        'frmThreads
> >        '
> >        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
> >        Me.ClientSize = New System.Drawing.Size(292, 266)
> >        Me.Controls.Add(Me.Label1)
> >        Me.Controls.Add(Me.Button2)
> >        Me.Controls.Add(Me.Button1)
> >        Me.Name = "frmThreads"
> >        Me.Text = "frmThreads"
> >        Me.ResumeLayout(False)
> >
> >    End Sub
> >
> > #End Region
> >
> >    Private _lengthyProcess As LengthyProcess
> >    Private _counter As Integer
> >    Private dlgIvokeProgress As New dlgProgress(AddressOf ShowProgress)
> >
> >    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
> > System.EventArgs) Handles Button1.Click
> >        _lengthyProcess = New LengthyProcess
> >        AddHandler _lengthyProcess.Progress, AddressOf
> > LengthyProcess_Progress
> >
> >        Dim start As New Threading.Thread(AddressOf StartProcess)
> >        start.Start()
> >    End Sub
> >
> >    Private Sub StartProcess()
> >        _lengthyProcess.Start()
> >    End Sub
> >
> >    Private Sub LengthyProcess_Progress(ByVal counter As Integer)
> >        _counter = counter
> >        Me.Invoke(dlgIvokeProgress)
> >    End Sub
> >
> >    Private Delegate Sub dlgProgress()
> >    Private Sub ShowProgress()
> >        Me.Label1.Text = _counter
> >    End Sub
> >
> >    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
> > System.EventArgs) Handles Button2.Click
> >        MsgBox(_lengthyProcess.Cancel)
> >    End Sub
> > End Class
> >
> > Public Class LengthyProcess
> >
> >    Public Event Progress(ByVal counter As Integer)
> >
> >    Private _counter As Integer
> >    Private _cancelled As Boolean
> >    Private _stopped As Threading.ManualResetEvent
> >
> >    Public Sub Start()
> >        Do While _cancelled = False
> >            _counter += 1
> >
> >            Threading.Thread.Sleep(100)
> >            RaiseEvent Progress(_counter)
> >        Loop
> >
> >        If _cancelled = True Then
> >            _stopped.Set()
> >        End If
> >    End Sub
> >
> >    Public Function Cancel() As Boolean
> >        _stopped = New Threading.ManualResetEvent(False)
> >        _cancelled = True
> >
> >
> >        'this will ALWAYS return FALSE
> >        'how ever if i were to display a messagebox here and THEN
> >        'execute the WaitOne(), it will return TRUE
> >        'MsgBox("WaitOne will now return TRUE")
> >
> >        Return _stopped.WaitOne(5000, False)
> >    End Function
> > End Class
>
>
>