等待所有异步下载完成

时间:2019-07-23 00:20:39

标签: c# asynchronous download

我正在尝试运行一些下载多个文件的代码,并等待所有代码完成后再继续执行代码。

我当前正在运行这部分代码,它实际上确实按照预期的方式下载了所有内容,但不会恢复执行。程序只是冻结了。

如果您能帮助我解决这个问题,那就太好了。

multiprocessing.Pool().starmap()

2 个答案:

答案 0 :(得分:1)

tl; dr

您将要await个任务和async个方法

await Task.WhenAll(tasks.ToArray());
// ...
return await wc.DownloadFileTaskAsync(...);

这假定签名为async的方法。

好的,为什么?

不考虑这些void方法是事件处理程序的可能性-在winforms,webforms,wpf等UI线程上运行-您的WaitAll blocking 方法。通过等待所有这些,您正在阻塞当前线程。通过等待所有这些,您就可以允许它们异步运行。

如果这些无效方法也正在UI线程上运行,那将是第二个类似的问题。

一个更长的例子

using System.Linq;
using System.Threading.Tasks;

/// <summary>
/// Await all files in a list of paths to download
/// </summary>
public async Task DownloadFilesAsync(IWebClient client, IEnumerable<string> filePaths) 
{
    var downloadTasks = filePaths
        .Select(f => DownloadFileAsync(client, f))
        .ToArray();

    // if any fails, we return that an error occurred.  If you prefer to let "some" of
    // those downloads fail, just move the try/catch into the individual file download 
    // method below.
    try {
        // wait for all to complete
        await Task
            .WhenAll(downloadTasks)
            .ConfigureAwait(false);

        return Task.CompletedTask;
    } catch (Exception e) {
        // I just made this up as an example; find the right type 
        // of result using intellisense in your IDE
        return Task.ErroredTask(e);
    }
}

/// <summary>
/// Await a single file download
/// </summary>
public async Task DownloadFileTaskAsync(IWebClient client, string filePath) 
{
    // set up request in the client, etc
    var url = "http://example.com";

    return await client
        .DownloadFile(url, filePath)
        .ConfigureAwait(false);

}

答案 1 :(得分:0)

您应该考虑使用Microsoft的Reactive Framework(aka Rx)-NuGet System.Reactive并添加using System.Reactive.Linq;-然后您可以执行以下操作:

var query =
    from x in targets.ToObservable().Select((t, c) => new { t, c })
    where x.t.EndsWith(".vtt", StringComparison.InvariantCultureIgnoreCase)
    let target = $"{base_url}{sub_id}/{x.t}"
    let name = $"{sub_id}.{x.c}.vtt"
    from status in
        Observable
            .Using(
                () => new WebClient(),
                wc =>
                {
                    var progress =
                        Observable
                            .FromEventPattern<DownloadProgressChangedEventHandler, DownloadProgressChangedEventArgs>(
                                h => wc.DownloadProgressChanged += h, h => wc.DownloadProgressChanged -= h)
                            .Select(ep => $"{ep.EventArgs.ProgressPercentage}% downloaded.");

                    var completed =
                        Observable
                            .FromAsync(() => wc.DownloadFileTaskAsync(target, $"{Environment.CurrentDirectory}/Subs/{name}"))
                            .Select(z => $"{target} was downloaded.");

                    return progress.Merge(completed);
                })
    select new { target, status };

这是一个异步查询,它处理所有并行调用-在完成每个调用时处置WebClient

您可以等待所有结果,如下所示:

query
    .Do(x => Console.WriteLine(x.status))
    .ToArray()
    .Wait();

但是更惯用的处理方式是:

IDisposable subscription =
    query
        .Subscribe(
            x => Console.WriteLine(x.status),
            ex => { /* handle an exception */ },
            () => { /* when everything is done */ });

这将尽快处理结果,并在完成后为您提供运行一些代码的机会。

如果需要编组到UI线程,则可以执行以下操作:

IDisposable subscription =
    query
        .ObserveOnDispatcher() // or .ObserveOn(instanceOfForm)
        .Subscribe(
            x => Console.WriteLine(x.status),
            ex => { /* handle an exception */ },
            () => { /* when everything is done */ });

要在需要提早停止的情况下停止下载,只需执行subscription.Dispose();