Arsen Zahray
Arsen Zahray

Reputation: 25367

How to add pause/resume functionality to an application?

I'm writing an app, where most of the work is done by background threads (10 - 500 threads).

I'd like to add pause/resume functionality.

Before, you could do that with Thread.Suspend and Thread.Resume. But those functions are considered obsolete now.

Is there anything that would let me do the same with equal ease?

I'm writing the software in c#

Upvotes: 5

Views: 888

Answers (4)

Jim Mischel
Jim Mischel

Reputation: 134125

Having written a high performance crawler in C#, I can say with some authority that explicitly managing dozens or hundreds of threads is not the best way to go. It can be done (I did it), but it's painful in the extreme.

That said . . .

If your application is written the way I think, then each thread does something like this:

while (!Shutdown)
{
    // get next url to crawl from somewhere
    // download the data from that url
    // do something with the data
}

Pausing the threads between downloads is pretty easy. I would suggest making two ManualResetEvent instances: one for continue, and one for shutdown. These are static so that all crawler threads can access them:

static ManualResetEvent ShutdownEvent = new ManualResetEvent(false);
static ManualResetEvent ContinueEvent = new ManualResetEvent(true);

Then, each thread uses WaitAny in a loop:

WaitHandle[] handles = new WaitHandle[] { ShutdownEvent, ContinueEvent };
while (true)
{
    int handle = WaitHandle.WaitAny(handles);  // wait for one of the events
    if (handle == -1 || handle >= handles.Length)
    {
        throw new ApplicationException();
    }

    if (handles[handle] = ShutdownEvent)
       break;  // shutdown was signaled

    if (handles[handle] == ContinueEvent)
    {
        // download the next page and do something with the data
    }
}

Note that when I defined the handles array, I specified ShutdownEvent first. The reason is that if multiple items are signaled, WaitAny returns the lowest index that corresponds to a signaled object. If the array were populated in the other order, then you wouldn't be able to shut down without pausing first.

Now, if you want the threads to shut down, call ShutdownEvent.Set. And if you want the threads to pause, call ContinueEvent.Reset When you want the threads to resume, call ContinueEvent.Set.

Pausing in the middle of a download is a bit more difficult. It's possible to do, but the problem is that if you pause for too long the server might timeout. And then you'll have to restart the download from the beginning or, if the server and your code support it, restart the download from the point at which you left off. Either option is rather painful, so I wouldn't suggest trying to pause in the middle of a download.

Upvotes: 2

Martin James
Martin James

Reputation: 24907

Not really. Suspend/Resume are really simple and work fine until they crash your app, eg. by suspending a thread that has the memory manager or file system locked up :(

The usual, slightly more complex, approach is to find somewhere in your threads where you can wait. In you case, I'm guessing that most of the threads are normally blocked on some IO call anyway and so are 'suspended', so a good place to enforce a 'suspend' is straight after the read in order to catch those threads where the read does return.

You can do the actual suspending by checking a global boolean 'isRunning' flag as suggested by @Andrey and, if a suspend is needed, block on a global ManualResetEvent. To suspend, clear the event and then the flag. To resume, set the flag and then the event.

If using globals makes you feel nauseous, you can pass in the ctor a common instance of some class containing the flag, event and 'suspend(), 'resume()' and 'checkForSuspend()' methods.

Rgds, Martin

Upvotes: 1

dashton
dashton

Reputation: 2714

As a caveat regarding what i'm about to say: It's not clear what your app does; there are many simple methods for using thread pool threads such as TPL, background workers etc.

However, if you have threads that you have created (not threadpool) and you want them to communicate then use Monitor.Wait and Monitor.Pulse with a boolean blocking condition.

e.g:

    bool _isPaused;

void DoWork()
{
        while (true)
        {
            lock (_locker)
            {
                while (_isPaused) Monitor.Wait(_locker);

                // your worker code here

            }

        }
}
        // 
void UnPause()
{
        lock (_locker)
        {
            _isPaused=false;
            Monitor.PulseAll(_locker);
        }
}

Upvotes: 1

Nick Butler
Nick Butler

Reputation: 24433

What does your app do?

500 threads is far too many - that's 1/2 GB of committed memory just for the stacks. And then you have all the context switching.

Good that you want to get rid of the Suspend and Resume calls, but I suggest you have a look at your architecture first - can you move to APM methods ( BeginXXX / EndXXX )?

Upvotes: 2

Related Questions