Reputation: 25367
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
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
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
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
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