使用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();     
 }

解决方法

这是一个 TPL Dataflow 实现。使用了两个块,一个 TransformBlock<Uri,string> 用于下载 URL,一个 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 将在后台下载和提取,以一种即发即忘的方式。


注意:我在异步方法 AsyncDownloadFile 中添加了 ExtractFile 后缀,以符合 guidelines

,

我想我会简化我的生活:

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)您的方法以异步方式运行将使必须阅读代码的人受益,尤其是互联网上没有智能感知/调用方法定义的人