3

I have a windows form which utilizes a backgroundworker. The backgroundworker instantiates an object and then executes a method in that object. My problem is that when I use backgroundworker.CancelAsync the method running on the remote object does not stop. In the example below, the dowork method continues to execute after button cancel is clicked. FYI, dowork is looping thru a spreadsheet and doing some data manipulation based on the rows in the spreadsheet.

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        myObject newObject = new myObject();
        newObject.dowork();

        if (backgroundWorker1.CancellationPending)
        {
            e.Cancel = true;
            backgroundWorker1.ReportProgress(0);
            return;
        }
    }

     private void btnCancel_Click(object sender, EventArgs e)
    {
       if (backgroundWorker1.IsBusy) backgroundWorker1.CancelAsync();
    }

Thoughts?

Thanks

kurt
  • 31
  • 3
  • 2
    Well does newObject.dowork() check for backgroundWorker1.CancellationPending? And how is myObject newObject = new myObject(); a new thread? – paparazzo Jul 17 '15 at 20:03
  • 4
    I'm sure `newObject.dowork()` is blocking, so the background worker never evaluates the cancellation statements. You can create a task instead of using a background worker but its difficult to "cancel" synchronous methods... – Ron Beyer Jul 17 '15 at 20:03
  • As Blam suggests (read between the lines), pass a reference to your BackgroundWorker into your myObject instance (via the Constructor would be good) and check the CancellationPending flag within your loop. – Idle_Mind Jul 17 '15 at 20:57

1 Answers1

2

In btnCancel_Click you must pass the cancellation request to your worker object; otherwise, it will never be notified. The BackgroundWorker.CancelAsync() does not do anything just simply sets the BackgroundWorker.CancellationPending property, notifying the consumer of the BackgroundWorker (the UI, not the executed task) that your task has been cancelled.

So what you need:

MyObject myObject;

// This method is executed on the worker thread. Do not access your controls
// in the main thread from here directly.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    myObject = new MyObject();

    // The following line is a blocking operation in this thread.
    // The user acts in the UI thread, not here, so you cannot do here
    // anything but wait.
    myObject.DoWork();

    // Now DoWork is finished. Next line is needed only to notify
    // the caller of the event whether a cancel has happened.
    if (backgroundWorker1.CancellationPending)
        e.Cancel = true;

    myObject = null;
}

private void btnCancel_Click(object sender, EventArgs e)
{
   if (backgroundWorker1.IsBusy)
   {
       backgroundWorker1.CancelAsync();

       // You must notify your worker object as well.
       // Note: Now you access the worker object from the main thread!
       // Note2: It would be possible to pass the worker to your object
       //        and poll the backgroundWorker1.CancellationPending from there,
       //        but that would be a nasty pattern. BL objects should not
       //        aware of the UI components.
       myObject.CancelWork();
   }
}

And how should you implement the notification:

public class MyObject
{
    // normally you should use locks to access fields from different threads
    // but if you just set a bool from one thread and read it from another,
    // then it is enough to use a volatile field.
    private volatile bool isCancelRequested;

    // this will be called from the main thread
    public void CancelWork()
    {
        isCancelRequested = true;
    }

    // This method is called from the worker thread.
    public void DoWork()
    {
        // Make sure you poll the isCancelRequested field often enough to
        // react to the cancellation as soon as possible.
        while (!isCancelRequested && ...)
        {
            // ...
        }
    }
}
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • This comment `Make sure you poll the isCancelRequested field often enough to react to the cancellation as soon as possible` isn't trivial. – Conrad Frix Jul 28 '15 at 14:48
  • @Conrad Frix: Yes, of course. :) Especially if you have to return from a deep method call stack. But I would not recommend to throw an exception just to ourselves or to call Thread.Abort. They seem convenient but are very unclean. Actually, I would not recommend BackgroundWorker either, since TPL is available... – György Kőszeg Jul 28 '15 at 15:01