使用C#以同步方式下载文件并异步提取下载的文件

Download files in synchronous manner and extract downloaded files asynchronously using C#

我有一些大型 zip 文件的 URL 列表。我正在使用 HttpClient 循环下载文件。我必须在下载过程后解压缩文件。我想在每个文件完成下载时开始解压,而不是等待整个下载过程完成。文件下载应该以同步方式(一个接一个)进行,提取应该与每个下载的文件异步进行。我的应用程序正在使用 .Net Framework 4.5.2 和 C#7。

在下面的代码中,文件下载也是异步的。由于带宽问题,我试图避免异步下载。

public void DownloadAndExtract()
{
        IDataReader dr = _myDB.GetFileUrl();
        while (dr.Read())
        {
                DownloadFile(new Uri(dr["URL"].ToString())).ContinueWith(task1 =>
                {
                      var downloadedFilePath = task1.Result.fileName;
                      ExtractFile(downloadedFilePath).GetAwaiter().GetResult();
                });
         }
         dr.Close();     
 }

我想我会简化我的生活:

public async Task DownloadAndExtractAsync()
{
    using(IDataReader dr = _myDB.GetFileUrl()){
        while (dr.Read())
        {
          var f = await DownloadFileAsync(new Uri(dr["URL"].ToString()));
          _ = ExtractFileAsync(f.fileName);
        }
    }
}

对此唯一让我感到不安的是,它可能会使数据库连接保持打开状态的时间过长。也许:

public async Task DownloadAndExtractAsync()
{
    DataTable dt = new DataTable();
    using(IDataReader dr = _myDB.GetFileUrl())
       dt.Load(dr);

    foreach(DataRow dr in dt.Rows)
    {
        var f = await DownloadFileAsync(new Uri(dr["URL"].ToString()));
        _ = ExtractFileAsync(f.fileName);
    }
}

后缀(使用 ...Async)以异步方式运行的方法将使必须阅读代码的人受益,尤其是互联网上无法受益于 intellisense/definition 的人你调用的方法

这里有一个TPL Dataflow implementation. Two blocks are used, one TransformBlock<Uri, string> for downloading the URLs, and one ActionBlock<string>用于提取文件。

private void DownloadAndExtract()
{
    var downloadBlock = new TransformBlock<Uri, string>(async uri =>
    {
        var downloadedFile = await DownloadFileAsync(uri);
        return downloadedFile.fileName;
    }, new ExecutionDataflowBlockOptions()
    {
        MaxDegreeOfParallelism = 1
    });

    var extractBlock = new ActionBlock<string>(async filePath =>
    {
        await ExtractFileAsync(filePath);
    }, new ExecutionDataflowBlockOptions()
    {
        MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded
    });

    downloadBlock.LinkTo(extractBlock,
        new DataflowLinkOptions() { PropagateCompletion = true });

    IDataReader dr = _myDB.GetFileUrl();
    while (dr.Read())
    {
        downloadBlock.Post(new Uri(dr["URL"].ToString()));
    }
    dr.Close();
    downloadBlock.Complete();
    extractBlock.Completion.Wait();
}

在将所有 Uri 发布到 downloadBlock 之前将它们存储在列表中会更安全。使用上面的代码,数据库中一个格式错误的 URL 将导致 DownloadAndExtract 方法失败,而以前的 URL 将在后台下载和提取,以一种即刻即忘的方式。


注意:我在异步方法DownloadFileExtractFile后面加了Async后缀,以符合guidelines.