2

I have some code that calls a method to download a file:

private async Task DownloadFile()
{
    WebClient client = new WebClient();

    var downloadTask =
        Task.Run(
            () =>
                client.DownloadFile("http://www.worldofcats.com/bigkitty.zip",
                    "c:\\cats\\"
         );

    await downloadTask;
}

To invoke this method, I do this:

var downloadTask = DownloadFile();

await downloadTask;

Since it's part of a forms app, this causes no issue while it downloads with the UI being unresponsive. The only problem is, the DownloadFile method has no timeout, and sometimes it might go wrong or hang, so I need to put a timeout in.

If I use Task.Wait(x); then it blocks the UI thread. I think I can possibly use await Task.WhenAny(downloadTask, () => Thread.Sleep(50000)); but I am not sure if this is the best way.

So my question is, what should I do to solve this, and how can I cleanup my task if it's forcibly terminated? (Or do I have to worry about that?)

NibblyPig
  • 51,118
  • 72
  • 200
  • 356

2 Answers2

3

Older solution, not feasible for tasks that are not cancellation-aware

You should pass a CancellationToken:

private async Task DownloadFile()
{
    WebClient client = new WebClient();
    using(var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60))
    {        
        var downloadTask =
            Task.Run(
                () =>
                    client.DownloadFile("http://www.worldofcats.com/bigkitty.zip",
                        "c:\\cats\\"),
                 cts.Token
             );
        await downloadTask;
    }
}

Now when you await DownloadFile() you can wrap it in a try/catch block to catch the TaskCanceledException (or OperationCanceledException):

try
{
    await DownloadFile();   
}
catch(TaskCanceledException)
{
    //Timeout!
}

[EDIT]

As was noticed in comments, you can't cancel a task that's not cancellation-aware - somehow I forgot about that (boo!). But no worries, you can fix that by using DownloadFileTaskAsync and CancelAsync, so you don't even need cancellation token:

var downloadTask = client.DownloadFileTaskAsync("http://www.worldofcats.com/bigkitty.zip",
                        "c:\\cats\\");
var timerTask = Task.Delay(TimeSpan.FromSeconds(60));

await Task.WhenAny(downloadTask, timerTask);
client.CancelAsync(); // This does nothing if there's no operation in progress, as noted in documentation
Community
  • 1
  • 1
Patryk Ćwiek
  • 14,078
  • 3
  • 55
  • 76
-1

check is out:

private static async void Test()
{
    var source = new CancellationTokenSource();
    var watcher = Task.Delay(TimeSpan.FromSeconds(4), source.Token);
    var downloadTask = Task.Factory.StartNew(() =>
                                             {
                                                 //.. Simulating a long time task
                                                 Thread.Sleep(TimeSpan.FromSeconds(10));
                                             },
        source.Token);
    await Task.Run(() => { Task.WaitAny(watcher, downloadTask); });
    source.Cancel();
    if (!downloadTask.IsCompleted)
        Console.WriteLine("Time out!");
    else
        Console.WriteLine("Done");
}
Mohammad Mirmostafa
  • 1,720
  • 2
  • 16
  • 32
  • 2
    `await Task.Run(() => { Task.WaitAny(watcher, downloadTask); })` Please don't write code like this. `await Task.WhenAny(watcher, downloadTask)` is *much* better. – svick Sep 09 '13 at 11:24