在等待带有进度报告的 DownloadFileAsync() 时暂停主线程

Pausing main thread while waiting for DownloadFileAsync() with progress report

正如您在代码中间看到的那样,有一个丑陋的线程阻塞代码等待文件下载并在命令行中启用进度报告。我的意思是,它仍然比 Thread.Sleep() 或忙着等待要好,对吧?不管怎样,我确实知道 Wait/Pulse,但我不知道如何在这里应用它。

完全重构我的代码以更好地适应单个异步操作是否是最干净的解决方案?是否可以覆盖 WebClient class 中的某些内容以利用 Wait/Pulse 类型的等待?

有问题的项目和函数:Github

相关片段:

static void GetPatch(KeyValuePair<string, string> entry, string part)
{
    string url = entry.Key,
        fname = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1),
        path = G.patchPath + "\" + fname;
        bool exists = File.Exists(path);
    Console.Write(fname + " ... ");
    string message = "local";
    if ((exists && GetSHA1(path) != entry.Value) || !exists)
    {
        if (exists) File.Delete(path);
        G.wc.DownloadProgressChanged += Wc_DownloadProgressChanged;
        G.wc.DownloadFileAsync(new Uri(url), part);
        while (G.wc.IsBusy)
        {
            // There must be a better way
            new System.Threading.ManualResetEvent(false).WaitOne(200);
        }
        G.wc.DownloadProgressChanged -= Wc_DownloadProgressChanged;
        message = "done";
    }
    if (File.Exists(part)) File.Move(part, path);
    G.patchFNames.Enqueue(fname);
    Green(message);
}

private static void Wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    int p = e.ProgressPercentage;
    p = p < 100 ? p : 99;
    Console.Write("{0:00}%\b\b\b", p);
}

请耐心等待,这是我用 C# 编写的第一个项目,我是 OOP 和 C# 的绝对新手。

让我从评论中的 url 复制代码:

public void DownloadFile(Uri uri, string desintaion)
{
  using(var wc = new WebClient())
  {
    wc.DownloadProgressChanged += HandleDownloadProgress;
    wc.DownloadFileCOmpleted += HandleDownloadComplete;

    var syncObj = new Object();
    lock(syncObject)
    {
       wc.DownloadFileAsync(sourceUri, destination, syncObject);
       //This would block the thread until download completes
       Monitor.Wait(syncObject);
    }
  }

  //Do more stuff after download was complete
}

public void HandleDownloadComplete(object sender, AsyncCompletedEventArgs args)
{
   lock(e.UserState)
   {  
      //releases blocked thread
      Monitor.Pulse(e.UserState);
   }
}


public void HandleDownloadProgress(object sender, DownloadProgressChangedEventArgs args)
{
  //Process progress updates here
}

您的高级代码应如下所示

public async Task StartFileDownload()
{
    var downloadTask = StartDownload();
    var monitoringTask = StartMonitoringProgress();

    await Task.WhenAll(downloadTask, monitoringTask);
}

您的监控任务应该是每 N 毫秒检查一次下载进度并更新进度条。尽管您不在 UI 流程中,因此无法直接执行此操作,但您将必须 "dispatch" UI 更新。

关于上述解决方案的注意事项:

如果异步下载没有启动(例如,如果您在 WPF 应用程序中),您可能需要设置 Thread SynchronizationContext 以使用线程池。如果这样做,则需要确保不要更改回调中的任何 UI 元素。

SynchronizationContext orig = SynchronizationContext.Current;
// Use thread pool and not the SyncContext for WPF
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
wc.DownloadDataAsync(new Uri(url), syncObject);
SynchronizationContext.SetSynchronizationContext(orig);