如何在没有阻止UI线程的情况下超时任务?

时间:2013-09-09 11:01:05

标签: c# task-parallel-library

我有一些代码调用下载文件的方法:

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

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

    await downloadTask;
}

要调用此方法,我执行此操作:

var downloadTask = DownloadFile();

await downloadTask;

由于它是表单应用程序的一部分,因此在UI无响应的情况下下载时不会产生任何问题。唯一的问题是,DownloadFile方法没有超时,有时它可能出错或挂起,所以我需要暂停。

如果我使用Task.Wait(x);,那么它会阻止UI线程。我想我可以使用await Task.WhenAny(downloadTask, () => Thread.Sleep(50000));,但我不确定这是否是最佳方式。

所以我的问题是,我应该怎么做才能解决这个问题,如果它被强行终止,我怎样才能清理我的任务呢? (或者我必须担心吗?)

2 个答案:

答案 0 :(得分:3)

较旧的解决方案,对于不支持取消的任务

不可行

您应该传递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;
    }
}

现在当你await DownloadFile()时,你可以将其包裹在try/catch块中以捕获TaskCanceledException(或OperationCanceledException):

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

[编辑]

正如评论中所注意到的那样,你无法取消一个不能取消注意的任务 - 不知怎的,我忘记了这一点(嘘!)。但不用担心,您可以使用DownloadFileTaskAsyncCancelAsync来解决这个问题,因此您甚至不需要取消令牌:

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

答案 1 :(得分:-1)

检查结束:

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");
}