等待异步下载完成

时间:2019-01-26 00:09:06

标签: c# download

这是我的全部源代码。我想下载x个文件(假设我们现在有2个文件要下载)。

我的主要观点是等待所有下载完成,然后继续。 我尝试了几点。

Task.WhenAll(List)->无法以我的实现方式工作

await->不可能在MainWindow函数中没有异步。

工作流程如下。 开始下载->已下载所有文件->显示Debug.Print

Atm:源代码输出为-> 所有下载完成 “下载完成” “下载完成”

应为: “下载完成” “下载完成” 所有下载完成

工作了几个小时,并没有进一步的步骤来解决问题。这是一个WPF应用程序。

    private volatile bool _download_completed;
    public bool DownloadCompleted { get { return _download_completed; } }


    public MainWindow()
    {

        InitializeComponent();
        ProcessService.FillProcessData();
        ProcessService.CloseAllProcesses();
        DownloadHelper.AddDownloadFiles();

        foreach(KeyValuePair<string,Uri> file in DownloadHelper.DownloadFiles)
        {
            DownloadFile(file.Key, file.Value);
        }
        Debug.Print("All Downloads done.");

    }

  private void DownloadFile(string Filename, Uri Uri)
    {

        Console.WriteLine(Filename + " " + Uri);
        _download_completed = false;
        WebClient client = new WebClient();
        client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCompleted);
        client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressCallback);
        client.DownloadFileTaskAsync(Uri, Filename);

    }

private void DownloadProgressCallback(object sender, DownloadProgressChangedEventArgs e)
    {
        this.Dispatcher.Invoke(() => {

            progressBar.Value = e.ProgressPercentage;
            StatusLabel.Content = e.ProgressPercentage + " % complete... ( " + e.BytesReceived + " / " + e.TotalBytesToReceive + ")";
        });
    }

    private void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
    {
        Console.WriteLine("Download completed");
        this.Dispatcher.Invoke(() =>
        {
            StatusLabel.Content = "Download Finished";
        });

        _download_completed = true;

    }


}

3 个答案:

答案 0 :(得分:0)

您需要使用任务来实现这一目标

首先使您的下载功能异步

    private async TaskDownloadFile(string Filename, Uri Uri)
        {

            Console.WriteLine(Filename + " " + Uri);
            _download_completed = false;
            WebClient client = new WebClient();
            client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCompleted);
            client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressCallback);

      await   client.DownloadFileTaskAsync(Uri, Filename);

        }

使方法异步,然后等待DownloadFileTaskAsync方法。

如果您要进行多次下载并没有太大意义,我建议删除DownloadFileCompeted和DownloadProgressChanged事件

现在,您已使方法async进行以下操作

var file1DownloadTask = TaskDownloadFile("filename","url");
var file2DownloadTask = TaskDownloadFile("filename","url");

这时您有两个任务

您可以使用Task.WaitAll(list_of_taks_goes_here)

Task.WaitAll(file1DownloadTask,file2DownloadTask )

wait all方法将等待所有任务完成,因此在这两个任务完成后调用下一行代码

然后,您可以在Task.WaitAll行之后更改ui或您需要执行的任何操作

您还可以执行以下操作

await TaskDownloadFile("filename","url");
await TaskDownloadFile("filename2","url");

将等待下载完成,然后再继续下一行,但是通过这种方式,您必须在编写代码时了解所有文件,使用第一种方法,您可以拥有一个文件列表,从中可以制作任务并将其传递给Task.WaitAll

答案 1 :(得分:0)

请注意,以下内容会将完整的下载内容下载到内存中,如果下载的文件很大,请考虑重写以在HttpClient上调用GetStreamAsync并流式传输到文件,但这将在合理的大小下完成。

您通常希望重用HttpClient,但在完成后进行处置,因此,如果这是WPF,则可以构造并在启动时进行保留并最终处置,但这可能也是合理的。

编辑:请注意,如果锁定UI线程成为问题,您可能需要在另一个线程上执行此操作并分派完成。

void Main()
{
    using (var client = new HttpClient())
    {
        var tasks = new List<Task>();
        var files = new Dictionary<string, string>();
        files.Add("c:\\temp\\MyFilename1.md", "https://raw.githubusercontent.com/aspnet/AspNetCore/master/README.md");
        files.Add("c:\\temp\\MyFilename2.md", "https://raw.githubusercontent.com/aspnet/AspNetCore/master/README.md");

        foreach (var file in files)
        {
            tasks.Add(DownloadFileAsync(client, file.Key, file.Value));
        }

        Task.WaitAll(tasks.ToArray());
    }
}

async Task DownloadFileAsync(HttpClient client, string filename, string url)
{
    var contents = await client.GetStringAsync(url);
    File.WriteAllText(filename, contents);
}

答案 2 :(得分:0)

  

我的主要观点是等待所有下载完成然后继续。

我鼓励您退后一步,考虑应用程序的UX设计。永远不要等待I / O并阻塞UI线程,这是一个好主意。

尤其是,您的ViewModel构造函数绝不能在I / O上 block 。 ViewModel构造函数应立即并同步完成,以便可以立即并同步显示您的UI。

现在,构造函数可以开始异步操作;没关系。它只是不应该等待它们完成。您的构造函数应将ViewModel初始化为显示“正在下载...”或类似内容的状态,并在下载完成(或随着下载进度)时,ViewModel可以更新其状态以反映该状态。 / p>

有关此模式的更多信息,请参阅我在data binding to asynchronous operations上的MSDN文章。