在等待下载完成时取消WebClient下载

时间:2018-02-28 16:27:58

标签: c# webclient cancellation webclient-download

寻找一种更普遍接受的等待WebClient的模式:

  • 下载文件(可能需要几百毫秒,或几分钟)
  • 在执行任何其他工作之前等待下载完成
  • 定期检查另一个班级的标志(bool)并取消下载(如果需要)(不能修改此课程)

约束:

  • 除非是Task.Run(async () => await method())
  • ,否则不能使用async / await
  • 当调用Download方法时,它只需要像返回字符串的普通方法一样行事
  • 可以使用.Net 4.5和Roslyn编译器
  • 中的任何功能
  • 使用WebClient.DownloadFileTaskAsyncDownloadFileAsync没有区别;只需要能够使用WebClient
  • 取消根据需要下载

目前的实施似乎有效,但似乎并不正确。除了使用while循环和Thread.Sleep在使用otherObject.ShouldCancel时定期检查WebClient时,是否有更普遍可接受的替代方案?

private string Download(string url)
{
    // setup work
    string fileName = GenerateFileName();

    // download file
    using (var wc = new WebClient()) 
    {
        wc.DownloadFileCompleted += OnDownloadCompleted

        Task task = wc.DownloadFileTaskAsync(url, fileName);

        // Need to wait until either the download is completed
        // or download is canceled before doing any other work
        while (wc.IsBusy || task.Status == TaskStatus.WaitingForActivation) 
        {
            if (otherObject.ShouldCancel) 
            {
                wc.CancelAsync();
                break;
            }

            Thread.Sleep(100);
        }

        void OnDownloadCompleted(object obj, AsyncCompletedEventArgs args)
        {
            if(args.Cancelled)
            {
                // misc work
                return;
            }

            // misc work (different than other work below)
        }
    }

    // Other work after downloading, regardless of cancellation.
    // Could include in OnDownloadCompleted as long as this
    // method blocked until all work was complete

    return fileName;
}

1 个答案:

答案 0 :(得分:1)

我希望这有用。 基本上你的包装器使用cancellationToken.Register(webClient.Cancel)注册回调;一旦cancelToken.Cancel()被调用,异步任务应抛出一个异常,您可以按如下方式处理:

public class Client
{
    public async Task<string> DownloadFileAsync(string url, string outputFileName, CancellationToken cancellationToken)
    {
        using (var webClient = new WebClient())
        {
            cancellationToken.Register(webClient.CancelAsync);

            try
            {
                var task = webClient.DownloadFileTaskAsync(url, outputFileName);

                await task; // This line throws an exception when cancellationTokenSource.Cancel() is called.
            }
            catch (WebException ex) when (ex.Message == "The request was aborted: The request was canceled.")
            {
                throw new OperationCanceledException();
            }
            catch (TaskCanceledException)
            {
                throw new OperationCanceledException();
            }

            return outputFileName;
        }
    }
}

尝试此示例的简单方法

    private async static void DownloadFile()
    {
        var cancellationTokenSource = new CancellationTokenSource();
        var client = new Client();

        var task = client.DownloadFileAsync("url",
            "output.exe", cancellationTokenSource.Token);

        cancellationTokenSource.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));

        cancellationTokenSource.Cancel();

        try
        {
            var result = await task;
        }
        catch (OperationCanceledException)
        {
            // Operation Canceled
        }
    }

在更现实的情况下,由于用户交互或回调而引发的事件将调用cancellationTokenSource.Cancel()。

<强>更新

另一种方法是订阅DownloadProgressChanged事件,并在调用回调时检查otherObject.ShouldCancel。

以下是一个例子:

public class Client
{
    public string Download(string url)
    {
        // setup work
        string fileName = GenerateFileName();

        // download file
        using (var wc = new WebClient())
        {
            wc.DownloadProgressChanged += OnDownloadProgressChanged;
            wc.DownloadFileCompleted += OnDownloadFileCompleted;

            DownloadResult downloadResult = DownloadResult.CompletedSuccessfuly;

            void OnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
            {
                if (otherObject.ShouldCancel)
                {
                    ((WebClient)sender).CancelAsync();
                }
            }

            void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
            {
                if (e.Cancelled)
                {
                    downloadResult = DownloadResult.Cancelled;
                    return;
                }

                if (e.Error != null)
                {
                    downloadResult = DownloadResult.ErrorOccurred;
                    return;
                }
            }

            try
            {
                Task task = wc.DownloadFileTaskAsync(url, fileName);
                task.Wait();
            }
            catch (AggregateException ex)
            {
            }

            switch (downloadResult)
            {
                case DownloadResult.CompletedSuccessfuly:

                    break;
                case DownloadResult.Cancelled:

                    break;
                case DownloadResult.ErrorOccurred:

                    break;
            }
        }

        // Other work after downloading, regardless of cancellation.
        // Could include in OnDownloadCompleted as long as this
        // method blocked until all work was complete

        return fileName;
    }
}

public enum DownloadResult
{
    CompletedSuccessfuly,
    Cancelled,
    ErrorOccurred
}