简体   繁体   中英

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?

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 .

如果你不想中止线程(实际上,它会导致世界崩溃和小猫燃烧),结束线程的唯一理智方法是对其中的工作进行粒化,并对取消令牌进行频繁检查(以其中任何一个为准)你喜欢它们的形式:内置[用于TPL],消息,锁定变量,信号量等等,并“干净地”结束线程。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM