views:

122

answers:

5

Let's say I have a background worker like this:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
             while(true)
             {
                  //Kill zombies
             }
        }

How can I make this background worker start and stop using a button on a WinForm?

+4  A: 
private bool _performKilling;

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
     while(true)
     {
         if (_performKilling)
         {
             //Kill zombies
         }
     }
}

private void StartKilling()
{
    _performKilling = true;
}

private void StopAllThatKilling()
{
    _performKilling = false;
}

Now call StartKilling and StopAllThatKilling from a button click handler or any other appropriate location.

You could start to worry about thread safety here, given that the _performKilling field is updated and accessed on different threads. However, accessing (and updating) a bool is an atomic operation, so it should require no extra locking mechanisms.

Update
Following the discussion around atomicity, freshness of data and order of access, here is an updated code sample that will address those issues by adding a lock around all read and write operations of the flag:

private bool _performKilling;
private object _lock = new object();

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
     while(true)
     {
         bool runLoop;
         lock (_lock)
         {
             runLoop = _performKilling;
         }
         if (runLoop)
         {
             //Kill zombies
         }
     }
}

private void StartKilling()
{
    lock (_lock)
    {
        _performKilling = true;
    }
}

private void StopAllThatKilling()
{
    lock (_lock)
    {
        _performKilling = false;
    }
}
Fredrik Mörk
You should add a sleep, to reduce cpu load when doing nothing
Am
Works just fine :D
Soo
"Accessing and updating a bool is an atomic operation". Is this because of the memory barrier?
Josh Smeaton
No, atomic means that the value true or false will be written in one go. A memory barrier dictates the order that writes or reads of the value are seen by another thread. There's nothing which says that the value written to _performKilling will ever be visible in another thread without such a mechanism. Atomicity means that the value read will always either be true or false, it does not mean that the background thread will ever see the most recently written value.
Pete Kirkham
They are atomic but to be on the safe side I would likely mark it volatile. This tight loop doesn't take breather either. It's constantly building data as fast as it can go, I wouldn't post any invalidate calls from this loop for instance.
Steve Sheldon
Definitely mark the flag volatile otherwise you might see heisenbugs.
spender
Yes, I should have clarified that by *thread safety* I was referring to the fact that the value will be written and read "in one go". Marking the flag `volatile` might steer up the order of access a bit (but not completely), so to be really on the safe side when it comes to order of access and freshness of data, some sort of locking mechanism should probably be introduced (I'll update the answer with that).
Fredrik Mörk
+1  A: 

I haven't tested this, I have code somewhere that I'll have to see exactly what I did, but something like this is an adaptation of Fredrik's answer:

private bool _performKilling;
private object _lockObject = new object();

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
     while(true)
     {

         if (_performKilling)
         {
             //Kill zombies
         }
         else
         { //We pause until we are woken up so as not to consume cycles
             Monitor.Wait(_lockObject);
         }
     }
}

private void StartKilling()
{
    _performKilling = true;
    Monitor.Pulse(_lockObject);
}

private void StopAllThatKilling()
{
    _performKilling = false;
]
AaronLS
Ah, of course; nice solution. I must be tired not coming up with it from the start myself ;)
Fredrik Mörk
+8  A: 

Maybe you can use a manualresetevent like this, I didn't debug this but worth a shot. If it works you won't be having the thread spin its wheels while it's waiting

ManualResetEvent run = new ManualResetEvent(true);

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
{ 
     while(run.WaitOne()) 
     { 
         //Kill zombies 
     } 
} 

private void War() 
{ 
    run.Set();
} 

private void Peace() 
{ 
    run.Reset();
}
Steve Sheldon
+1 Its great to see another soul that really understands .NET threading. ;) I think the use of a WaitHandle is always the better choice in these situations, as they intrinsically take care of atomicity and other multithreading issues for you (assuming you choose the right kind of WaitHandle).
jrista
`while (true) { if (run.WaitOne()) { } }`? Seems a little redundant, doesn't it? Why not `while (run.WaitOne()) { }`?
Dan Tao
It is indeed, I thought that myself after posting, i'll update it
Steve Sheldon
+3  A: 

Use the CancelAsync method.

backgroundworker1.CancelAsync();

In your loop inside the worker thread.

if (backgroundWorker.CancellationPending) return;

This doesn't happen immediately.

Aaron Smith
+1: I don't get why everybody's saying to check `while (_someCustomBoolean)` when the `BackgroundWorker` class already has this functionality built in.
Dan Tao
+1  A: 

By stop do you really mean stop or do you mean pause? If you mean stop, then this is a piece of cake. Create a button click event handler for the button you want to be responsible for starting the background worker and a button click event handler for the one responsible for stopping it. On your start button, make a call to the background worker method that fires the do_work event. Something like this:

private void startButton_Click(System.Object sender, 
    System.EventArgs e)
{
    // Start the asynchronous operation.
    backgroundWorker1.RunWorkerAsync();
}

On your stop button, make a call to the method that sets the background worker's CancellationPending to true, like this:

private void cancelAsyncButton_Click(System.Object sender, 
    System.EventArgs e)
{   
    // Cancel the asynchronous operation.
    this.backgroundWorker1.CancelAsync();
}

Now don't forget to check for the CancelationPending flag inside your background worker's doWork. Something like this:

   private void KillZombies(BackgroundWorker worker, DoWorkEventArgs e)
   {
        while (true)
        {
            if (worker.CancellationPending)
           {   
              e.Cancel = true;
           }
        }
   }

And your doWork method:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
             BackgroundWorker worker = sender as BackgroundWorker;
             KillZombies(worker, e);
        }

I hope this can steer you in the right direction. Some further readings:

http://msdn.microsoft.com/en-us/library/b2zk6580(v=VS.90).aspx

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

http://msdn.microsoft.com/en-us/library/waw3xexc.aspx

vitorbal