如何在不阻塞 UI 线程的情况下使任务超时

本文关键字:情况下 任务 超时 线程 UI | 更新日期: 2023-09-27 18:30:43

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

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));但我不确定这是否是最好的方法。

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

如何在不阻塞 UI 线程的情况下使任务超时

较旧的解决方案,不适用于无法识别取消的任务

你应该通过一个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

检查出来了:

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");
}
相关文章: