Cancelling a task which retrieves URLs asynchronously
Asked Answered
P

2

7

I'm having a bit of a problem finding out how to cancel this task in C#. I don't exactly have a strong understanding of handling threads and I've tried Googling for some simple code examples to help me out but I've gotten really no where. Here's the piece of code I'm working on:

var tasks = urls.Select(url => Task.Factory.StartNew(
state =>
{
    using (var client = new WebClient())
    {

        lock (this)
        {

        // code to download stuff from URL

        }

    }
}, url)).ToArray();

    try
    {
       Task.WaitAll(tasks);
    }
    catch (Exception e)
    {
      textBox2.AppendText("Error: " + e.ToString());
    }

Where "urls" is an array of URLs. Is there a simple way to make it so that, when I click a button in my program, the downloading of the URLs is stopped completely? Also, the code snippet I pasted is in a function which backgroundWorker1 calls, which I suppose might make things a bit more complicated. (The reason why I have a backgroundWorker is so the UI doesn't lock up while it's downloading URLs.)

If that in any way is a bit confusing, here is an outline of what I was trying to achieve with my code:

  • I have an array of URLs, I'd like to download every URL asynchronously without locking up the UI.
  • I'd preferably like the user to stop the program from downloading URLs by clicking a button, pretty much cancelling the thread.
  • When the user clicks the button again, the program downloads the URLs all over again from that array.

Thanks in advance.

Picofarad answered 31/1, 2012 at 4:34 Comment(1)
Please don't include tags in your question title; it just makes more work for those of us that care about consistency on SO.Ectomy
A
1

Don't know if this is right way to do this or not, but I have been able to cancel tasks using the following code. I have created a form with ListBox and ProgressBar so I'm raising and handling ProgressChanged event of BackgroundWorker. Hope this helps you in some way.

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    CancellationTokenSource _tokenSource = new CancellationTokenSource();
    CancellationToken _token = _tokenSource.Token;

    var urls = e.Argument as IEnumerable<string>;

    _token = new CancellationToken();

    if (urls == null) return;

    var i = 0;
    var a = 100 / urls.Count();

    var sb = new StringBuilder();
    var t = urls.Select(url => Task.Factory.StartNew(
                    (u) =>{
                        using (var wc = new WebClient())
                        {
                            lock (this){
                                var s = wc.DownloadString(u.ToString());
                                sb.AppendFormat("{1}:{0}\r\n", "", u);
                            }
                        }

                    if (Worker.CancellationPending){
                        //MAGIC HAPPENS HERE, IF BackgroundWorker IS REQUESTED
                        //TO CANCEL, WE CANCEL CancellationTokenSource
                        _tokenSource.Cancel();
                    }

                    //IF CANCELATION REQUESTED VIA CancellationTokenSource
                    //THROW EXCEPTION WHICH WILL ADD TO AggreegateException
                    _token.ThrowIfCancellationRequested();

                    //YOU CAN IGNORE FOLLOWING 2 LINES
                    i += a;
                    Worker.ReportProgress(i, u);
    }, url, _token)).ToArray();

    try
    {
        Task.WaitAll(t);
    }
    catch (AggregateException age)
    {
        if (age.InnerException is OperationCanceledException)
            sb.Append("Task canceled");
    }
    catch (Exception ex)
    {
        sb.Append(ex.Message);
    }

    e.Result = sb;
}
Alwyn answered 31/1, 2012 at 11:9 Comment(1)
Wow, that code example was very useful, I think my own piece of code worked, and the thread was cancelled successfully. I don't want to get too excited yet though, something could always come up. :)Picofarad
C
1

With WebClient, you can use the CancelAsync method to cancel an asynchronous operation.

To cancel the tasks you're starting via Factory.StartNew, you should use a CancellationTokenSource. You need to pass CancellationTokenSource.Token to the tasks (and you can ask if the token is canceled already using token.IsCancellationRequested), and you'd call CancellationTokenSource.Cancel() to set the token as cancelled.

Chairman answered 31/1, 2012 at 9:23 Comment(1)
Cheers, I'll make sure to remember this in regards to Factory.StartNewPicofarad
A
1

Don't know if this is right way to do this or not, but I have been able to cancel tasks using the following code. I have created a form with ListBox and ProgressBar so I'm raising and handling ProgressChanged event of BackgroundWorker. Hope this helps you in some way.

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    CancellationTokenSource _tokenSource = new CancellationTokenSource();
    CancellationToken _token = _tokenSource.Token;

    var urls = e.Argument as IEnumerable<string>;

    _token = new CancellationToken();

    if (urls == null) return;

    var i = 0;
    var a = 100 / urls.Count();

    var sb = new StringBuilder();
    var t = urls.Select(url => Task.Factory.StartNew(
                    (u) =>{
                        using (var wc = new WebClient())
                        {
                            lock (this){
                                var s = wc.DownloadString(u.ToString());
                                sb.AppendFormat("{1}:{0}\r\n", "", u);
                            }
                        }

                    if (Worker.CancellationPending){
                        //MAGIC HAPPENS HERE, IF BackgroundWorker IS REQUESTED
                        //TO CANCEL, WE CANCEL CancellationTokenSource
                        _tokenSource.Cancel();
                    }

                    //IF CANCELATION REQUESTED VIA CancellationTokenSource
                    //THROW EXCEPTION WHICH WILL ADD TO AggreegateException
                    _token.ThrowIfCancellationRequested();

                    //YOU CAN IGNORE FOLLOWING 2 LINES
                    i += a;
                    Worker.ReportProgress(i, u);
    }, url, _token)).ToArray();

    try
    {
        Task.WaitAll(t);
    }
    catch (AggregateException age)
    {
        if (age.InnerException is OperationCanceledException)
            sb.Append("Task canceled");
    }
    catch (Exception ex)
    {
        sb.Append(ex.Message);
    }

    e.Result = sb;
}
Alwyn answered 31/1, 2012 at 11:9 Comment(1)
Wow, that code example was very useful, I think my own piece of code worked, and the thread was cancelled successfully. I don't want to get too excited yet though, something could always come up. :)Picofarad

© 2022 - 2024 — McMap. All rights reserved.