HAL9000
HAL9000

Reputation: 1022

Waiting for threads to stop in Windows Services

I'm working on my first Windows Service project that involves some basic threading/parallelism. It's been pretty intense so far but I'm slowly beginning to understand threading (I guess we'll see about that...).

I have a Windows Service which will contain 2 loosely coupled worker threads. Thread A will be downloading files from a FTP server, unzipping/prepping them and saving to another directory where Thread B will have a FileSystemWatcher watching. Thread B will parse the files, do a number of things with the data, make some http calls, and finally archive and remove the files from the working directory.

I'm just beginning the work, and the problem I'm facing right now is how to wait for both thread A and thread B to return. I looked at this question: Thread.Join on multiple threads with timeout and had an idea.

The problem is if we're waiting on x threads to return with a x second timeout for each, with enough threads the service could appear non-responsive even under normal operation (I read the timeout before SCM complains is 30 seconds, right?). In addition, if we solve this problem by keeping track of time remaining to join as we loop over threads, the longer we wait on threads at the beginning of the collection of workers, the less time there is for remaining threads to return - so eventually with enough threads the service will appear non-responsive even though all the threads are returning within an expected period of time.

In this case I'll probably just thread.join both A and B on a 14 second timeout because I have only two workers and 14 seconds seems like enough time to return both.

If I had a variable number of worker threads (let's say > 8) would it be worth doing something like this? Would this work reliably?

Note: Don't use the following - it's a bad idea on multiple levels. See answers

    protected override void OnStop()
    {
        // the service is stopping - set the event
        Worker.ThreadStopEvent.Set();

        // stop the threads
        Parallel.ForEach(_workerThreads, t =>
        {
            t.Join(new TimeSpan(0, 0, 28));
        });
    }

Upvotes: 3

Views: 3786

Answers (3)

Remus Rusanu
Remus Rusanu

Reputation: 294217

Technically you don't have to wait until the threads are done (Join), but until they are reporting completion. So you can use an instance of a CountdownEvent shared between all the threads and wait for it. All threads would have to decrement the event in a finally block:

void WorkerThread (object args)
{
  try
  {
    // actual work here
    ... 
  }
  finally
  {
    sharedCountdown.Signal();
  }
}

and the when doing the shutdown and waiting for threads the finish:

// Notify all workers of shutdown
...

// Now wait for all workers to complete, up to 30 seconds
sharedCountdown.Wait(TimeSpan.FromSeconds(30));

Upvotes: 2

BrokenGlass
BrokenGlass

Reputation: 160852

Your current approach might not work well - you do not have any control over how many threads Parallel.ForEach really creates, not all the thread joins might run in parallel - it all depends on how these tasks are scheduled on the Thread pool. On top of that it's very inefficient since all the threads you spawn are basically just waiting.

If these worker threads do not have a critical task that must be finished on service shutdown I would make them all background threads instead - in that case you wouldn't have to deal with this problem at all.

Upvotes: 1

Jim Mischel
Jim Mischel

Reputation: 133975

I would suggest that rather than using a timeout for each thread, you instead set your event and then periodically do the Join with a shorter timeout. An extreme version would be:

Worker.ThreadStopEvent.Set();
Thread.Sleep(30000); // wait 30 seconds
// Now try to Join each thread, with a very short (20 ms, maybe) timeout.

The biggest problem with that approach is that you will always wait 30 seconds, even if all the threads stop in five seconds.

You could do better with a loop that sleeps for one second and then does the checks. You could then keep track of which threads have stopped, and exit the loop when all threads have stopped.

You're probably best off, though, using a CountdownEvent. Each thread signals when it's stopped, and the main thread waits until all threads have stopped. The Wait method allows you to specify a timeout value. After the wait, you can determine if any threads are still hung by calling Join with a very brief timeout.

Upvotes: 2

Related Questions