Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gracefully shutdown a thread

In an application I'm developing, I have a main form that simply sits there and displays log data, and a worker thread that autonomously does the work in a loop.

MyWorker worker = new MyWorker();
MainForm mainForm = new MainForm();

// Subscribe form to log event so log data gets displayed
worker.Log += mainForm.Log;

// Start the worker thread's MainLoop
new Thread(new ThreadStart(worker.MainLoop)).Start();

// Show the form (blocking)
Application.Run(mainForm);

// If we end up here, the form has been closed and the worker has to stop running            
worker.Running = false;

As you can see, whenever the form is closed, the worker thread should be stopped. The worker looks like this:

public class MyWorker
{
    public String Running { get; set; }

    public MyWorker()
    {
        Running = true;
    }

    public void MainLoop()
    {

        while (Running)
        {

            DoExtensiveWork1();
            if (!Running) return;

            DoExtensiveWork2();
            if (!Running) return;

            DoExtensiveWork3();
            if (!Running) return;

            DoExtensiveWork4();
            if (!Running) return;

            DoExtensiveWork5();         
            if (!Running) return;

            // We have to wait fifteen minutes (900 seconds) 
            // before another "run" can be processed
            for (int i = 0; i < 900; i++)
            {
                Thread.Sleep(1000);
                if (!Running) return;
            }
        }
    }
}

As you can see, I want the thread to be able to stop when switching between successive work operations, but not when within an operation. When an operation (DoExtensiveWorkN) has finished, its status and results are persisted do disk or database, so quitting while an operation is in progress (by, for example, Thread.Abort) is not an option.

However, I find this code I've just written repulsive to look at, especially the "wait loop" which sleeps for one second 900 times, to prevent the thread from idling for 15 minutes before detecting Running has been set to false.

I'd rather be able to throw some kind of event to stop the main loop as soon as it's finished a piece of work.

Can anyone point me in the right direction how to do this, or if a total rewrite is required because I totally misunderstood threading, show me somewhere where those principles are explained?

like image 293
CodeCaster Avatar asked Oct 20 '11 10:10

CodeCaster


1 Answers

You can tidy up both the running of the individual tasks and the 15 min wait loop considerably.

I'd suggest perhaps using something like this:

public class MyWorker
{
    private readonly ManualResetEvent _stopEvent = new ManualResetEvent(false);
    private readonly Action[] _workUnits;

    private bool Running
    {
        get { return !_stopEvent.WaitOne(0); }
    }

    public MyWorker()
    {
        _workUnits = new Action[]
        {
            DoExtensiveWork1,
            DoExtensiveWork2,
            DoExtensiveWork3,
            DoExtensiveWork4,
            DoExtensiveWork5
        };
    }

    public void Stop()
    {
        _stopEvent.Set();
    }

    public void MainLoop()
    {

        while (Running)
        {
            foreach (var workUnit in _workUnits)
            {
                workUnit();
                if (!Running) return;
            }           

            // We have to wait fifteen minutes (900 seconds) 
            // before another "run" can be processed
            if (_stopEvent.WaitOne(900000)) return;
        }
    }
}

Then to stop the process at the next appropriate point:

Worker.Stop();
like image 64
Iridium Avatar answered Nov 10 '22 07:11

Iridium