Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to cancel a long operation?

I have run into a problem that I'm not sure how to resolve. I have a method that contains a call from a service that populates a data table such as:

private void GetCases()
{
    try
    {
        //Setup our criteria
        Web_search_criteria myCriteria = new Web_search_criteria()
        {
            Keynum = 9, //Use Opening Date Key
            Range_start = "20100101", //01-01-2010
            Range_end = "20121223" //12-23-2013
        };


        //The myCases object is a datatable that is populated from the GetCasesDT() call.
        int status = db.server.GetCasesDT(myCriteria, ref myCases);
    }
    catch (Exception ex)
    {
        XtraMessageBox.Show("Unable to get data: " + ex.Message);
    }
}

So, as you can see, there isn't a way for me to grab just a few cases at a time - it just grabs it all.

Right now, I have this method being called in a BackgroundWorker's DoWork event, and I display another form with a marquee progress bar on it so the user will know that the system is actually doing something. On that form, I have a Cancel button that I subscribe to. Here is the code that does that:

    backgroundWorker1 = new BackgroundWorker() 
    { 
        WorkerSupportsCancellation = true,
        WorkerReportsProgress = true
    };

    //DoWork Event
    backgroundWorker1.DoWork += backgroundWorker1_DoWork;

    //Show the progress bar with subscription to event
    pb.btnCancel.Click += this.CancelBW;
    pb.Show();

    //Run the backgroundworker
    this.backgroundWorker1.RunWorkerAsync();

    //Don't lock up the application
    while (this.backgroundWorker1.IsBusy)
    {
        Application.DoEvents();
    }

I have tried to cancel the BackgroundWorker in the CancelBW event by using CancelAsync(), but then I read more and realize that doesn't work and would only work if I could break up that initial call so the BackgroundWorker could check on progress.

I have thought about using Thread instead of a BackgroundWorker, but have read that Aborting a thread will cause kittens to spontaneously combust.

So, what's the best way for me to handle this in which a user could cancel a long process?

like image 204
BrewingDev Avatar asked Dec 24 '13 15:12

BrewingDev


2 Answers

There is no fundamental way to properly cancel this operation. It's not CPU bound work, it's network bound work. You sent the request, you can't exactly pluck that request from the internet. All you can really do is have your code continue executing when your cancel conditions are met instead of waiting around for the operation to finish.

One major issue with your application is the busyloop that you have pumping the message queue:

while (this.backgroundWorker1.IsBusy)
{
    Application.DoEvents();
}

This is generally a bad idea, and is a practice that should be avoided. The idea of using a BackgroundWorker in the first place is for it to be asynchronous; you shouldn't be trying to block the current method until the BGW finishes.

While there are ways of incorporating a BGW into this; this particular case it probably easier to solve using the Task Parallel Library.

var cts = new CancellationTokenSource();

pb.btnCancel.Click += (s, e) => cts.Cancel();
pb.Show();

var task = Task.Factory.StartNew(() => GetCases())
    .ContinueWith(t => t.Result, cts.Token)
    .ContinueWith(t =>
    {
        //TODO do whatever you want after the operation finishes or is cancelled;
        //use t.IsCanceled to see if it was canceled or not.
    });

(I'd also suggest refactoring GetCases so that it returns the DataTable that it gets from the DB, rather than modifying an instance field. You can then access it through the Task's Result.

like image 196
Servy Avatar answered Oct 13 '22 00:10

Servy


If you don't want to abort threads (which, indeed, will cause the world to collapse and kittens to combust), the only sane way to end a thread is granularizing the work inside it and perform frequent checks for cancellation tokens (in whichever form you prefer them: built in [for the TPL], messages, locked variables, semaphores, or whatever) and end the thread "cleanly".

like image 21
Jcl Avatar answered Oct 13 '22 01:10

Jcl