2

I have a system tray application. The tray application has an icon and a context menu with some options. There is a menu called status which contains below toolstripmenuitems:

  • Start
  • Restart
  • Stop

They are enabled/disabled according to some conditions.

My system tray application, has a background thread does continuously check some conditions and do some work. Its main loop is below:

Do Until gAppExit Or Me.thdExit
 ' Check some conditions and do some work
Loop

gAppExit is a global variable that indicates whether user has exited from application through the 'exit' toolstripmenuitem.

thdExit indicates whether the thread should exit from the loop (I explain it later).

When user want to restart the background thread, he clicks on restart toolstripmenuitem and below sequence is done (Restart -> Halt -> WaitFinish):

Public Function ReStart() As Integer
    Dim result As Integer

    If IsNothing(ThreadBgWorker) Then
        Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
        Return RESULT_ERROR
    End If

    result = Me.Halt()
    If result <> RESULT_ERROR Then
        result = Me.Start()
    End If

    Return result
End Function

Public Function Halt() As Integer
    Dim result As Integer

    If IsNothing(ThreadBgWorker) Then
        Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
        Return RESULT_ERROR
    End If

    Me.thdExit = True
    result = Me.WaitFinish()

    Return result
End Function

Public Function WaitFinish() As Integer
    Dim result As Integer

    If IsNothing(ThreadBgWorker) Then
        Return RESULT_ERROR
    End If

    result = RESULT_ERROR

    Try
        'TODO:
        Console.WriteLine("Wait thread to finish (before Join)...")

        Me.ThreadBgWorker.Join()

        'TODO:
        Console.WriteLine("Thread finished... Continue restarting thread...")

        Me.RetVal = True

        result = Me.ThreadBgWorker.ManagedThreadId

        Logger.Write(String.Format("Task ""{0}"" correctly stopped.", _
                                   Me.ThreadBgWorker.Name))
    Catch ex As Exception
        Logger.Write(String.Format("Couldn't stop task ""{0}"": {1}", _
                                   Me.ThreadBgWorker.Name, ex.Message), _
                     LOGGING_CRITICAL_ERRORS_CATEGORY)
    End Try

    Return result
End Function

Public Function Start() As Integer
    Dim result As Integer

    If IsNothing(ThreadBgWorker) Then
        Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
        Return RESULT_ERROR
    End If

    result = RESULT_ERROR
    Me.thdExit = False

    Try
        If Me.ThreadBgWorker.ThreadState = Threading.ThreadState.Stopped Then
            Me.Create()
        End If

        Me.ThreadBgWorker.Start()   ' Start the new thread.
        result = Me.ThreadBgWorker.ManagedThreadId

        Logger.Write(String.Format("Task ""{0}"" correctly started.", _
                                   Me.ThreadBgWorker.Name))
    Catch ex As Exception
        Logger.Write(String.Format("Couldn't start task ""{0}"": {1}", _
                                   Me.ThreadBgWorker.Name, ex.Message), _
                     LOGGING_CRITICAL_ERRORS_CATEGORY)
    End Try

    Return result
End Function

Note that on Halt function, it awaits thread to finish by calling Me.ThreadBgWorker.Join() on function WaitFinish. Before calling WaitFinish function, thdExit is set to true in order to background thread can exit from main loop:

Do Until gAppExit Or Me.thdExit
 ' Check some conditions and do some work
Loop

ChangeStatusToStopped()

on exit loop, ChangeStatusToStopped() is called, and it is as below:

Private Delegate Sub ChangeStatusToStoppedDelegate()

Public Sub ChangeStatusToStopped()

    ' TODO:
    Console.WriteLine("Changing status to stopped...")
    System.Windows.Forms.Application.DoEvents()

    If MainMenu.InvokeRequired Then
        'TODO:
        Console.WriteLine("Invoke required!")

        MainMenu.Invoke(New ChangeStatusToStoppedDelegate(AddressOf ChangeStatusToStopped))
    Else
        'TODO:
        Console.WriteLine("Invoke NOT required!")

        Me.submnuStart.Enabled = True
        Me.submnuReStart.Enabled = False
        Me.submnuStop.Enabled = False
    End If

    ' TODO: 
    Console.WriteLine("Status changed to stopped.")
End Sub

What it does is to enable Start toolstripmenuitem and disable restart and stop toolstripmenuitems in the UI.

The problem is:

Within ChangeStatusToStopped method, when MainMenu.InvokeRequired is true, it calls:

MainMenu.Invoke(New ChangeStatusToStoppedDelegate(AddressOf ChangeStatusToStopped))

and then it gets stuck there, that is, else body:

        Me.submnuStart.Enabled = True
        Me.submnuReStart.Enabled = False
        Me.submnuStop.Enabled = False

is never executed. It seems like main thread is busy or some other problem in message pump.... Any ideas?

I have seen that line:

Me.ThreadBgWorker.Join()

in WaitFinish() function is reached before background thread exits main loop and despite thdExit has been set to true before doing Me.ThreadBgWorker.Join(), once join is performed, application gets stuck, background thread cannot exit main loop (seems application is busy or frozen).

Willy
  • 9,848
  • 22
  • 141
  • 284
  • 1
    Have you placed breakpoints in your thread to see if your Boolean value(s) are ever changed? I will attempt to reproduce this if I get the time. Also, are you using a `ContextMenu` or a `ContextMenuStrip`? – Visual Vincent Mar 20 '16 at 19:45
  • I share the same issue. I'm not able to reprocude this issue during debugging, because then everything works fine. However, in the debugger's Threads combobox I can see a number of threads, halted at Invoke() – AxD Dec 06 '17 at 20:55

0 Answers0