在BackgroundWorker中等待下载完成

3

我有一个带有进度条的对话框。当显示对话框时,后台工作者应使用WebClient下载两个文件并自动将它们复制到指定位置。

如何在复制新文件之前等待文件下载完成?

我已尝试过使用await,但我无法将Backgroundworker更改为异步方法。如何在worker中等待下载完成?

运行worker的代码:

private void fmUpdateingDatabaseDialog_Shown(object sender, EventArgs e)
{
    device.Connect();
    lbInformation.Text = "uploading database to " + device.FriendlyName;
    device.Disconnect();

    worker.WorkerReportsProgress = true;
    worker.WorkerSupportsCancellation = true;
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.ProgressChanged += 
        new ProgressChangedEventHandler(worker_ProgressChanged);
    worker.RunWorkerCompleted += 
        new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    worker.RunWorkerAsync();
}

在DoWork处理程序中编写代码(在实际代码中路径不为空):

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    //download files temporary
    WebClient client = new WebClient();
    client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
    client.DownloadFileTaskAsync(new Uri(""), Path.Combine(tempPath + ""));

    WebClient client2 = new WebClient();
    client2.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client2_DownloadProgressChanged);
    client2.DownloadFileTaskAsync(new Uri(""), Path.Combine(tempPath + ""));

    //upload files to phone
    device.Connect();
        device.TransferContentToDevice(Path.Combine(tempPath+""), folder.Id, folder, true);
        device.TransferContentToDevice(Path.Combine(tempPath+""), folder.Id, folder, true);
    device.Disconnect();
}
2个回答

2
您可以使用同步的WebClient方法(例如,使用DownloadFile而不是DownloadFileTaskAsync),或者直接使用async/await代替BackgroundWorker。在这种情况下,您主要进行I/O操作,因此asyncBackgroundWorker更适合。
一个async解决方案看起来像这样:
private async void fmUpdateingDatabaseDialog_Shown(object sender, EventArgs e)
{
  device.Connect();
  lbInformation.Text = "uploading database to " + device.FriendlyName;
  device.Disconnect();

  var progress = new Progress<T>(data =>
  {
    // TODO: move worker_ProgressChanged code into here.
  });
  await DownloadAsync(progress);
  // TODO: move worker_RunWorkerCompleted code here.
}

private async Task DownloadAsync(IProgress<T> progress)
{
  //download files temporary
  WebClient client = new WebClient();
  client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
  await client.DownloadFileTaskAsync(new Uri(""), Path.Combine(tempPath + ""));

  WebClient client2 = new WebClient();
  client2.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client2_DownloadProgressChanged);
  await client2.DownloadFileTaskAsync(new Uri(""), Path.Combine(tempPath + ""));

  //upload files to phone
  // TODO: Check for Async versions of these methods that you can await.
  //  If there aren't any, consider using Task.Run.
  device.Connect();
  device.TransferContentToDevice(Path.Combine(tempPath+""), folder.Id, folder, true);
  device.TransferContentToDevice(Path.Combine(tempPath+""), folder.Id, folder, true);
  device.Disconnect();
}

1
告诉我如果我错了,但是我认为我只能在异步方法中使用await。由于fmUpdateingDatabaseDialog_Shown不是异步的,我不知道如何实现这个。 编辑:我不想使用同步的WebClient,因为看到进度很重要。 - j0h4nn3s
请问您对于 async void 的看法是什么? 我读过一篇文章指出应该避免使用它,而我在 Stack Overflow 上经常看到您回答很多与 Task 相关的问题 :) 我觉得您的意见可能非常有趣。 - default
1
你应该避免使用 async voidasync void 只应用于事件处理程序(或逻辑上为事件处理程序的事物,例如 ICommand.Execute)。在这种情况下,fmUpdateingDatabaseDialog_Shown 是一个事件处理程序,因此 async void 是可接受的。DownloadAsync 不是事件处理程序,所以对于该方法使用 async void 是不正确的。我有一篇文章详细讲解了这个问题。 - Stephen Cleary

1
你可以使用类似以下的代码,其中使用了 "等待和脉冲" 机制来延迟代码执行,直到下载操作完成为止:
var locker = new object(); 

Thread t = new Thread(new ThreadStart(() =>
{
    lock (locker)
    {
        //peform your downloading operation, and wait for it to finish.
        client.DownloadFileTaskAsync(new Uri(""), Path.Combine(tempPath + ""));
        while (/* not yet downloaded */) { }; 
        //inform the parent thread that the download has finished.
        Monitor.Pulse(locker);
    }
}));

t.Start();

lock(locker)
{
    Monitor.Wait(locker);
}

然而,如果您有足够的资源,我建议重构您的代码,完全使用async-await方法(从而避免使用后台工作者)。 后台工作者是传统的异步方法之一,而推荐的方法是TAP。请参见Stephen Cleary的答案以获取如何执行此操作的示例。

3
请永远不要建议在这种情况下使用Thread.Sleep,无论是0还是1000毫秒的版本,也不要使用循环,而应该选择WaitHandles!(请参考我的详细说明@https://dev59.com/cGoy5IYBdhLWcg3wCpoz#8815944) - user57508
1
@roryap,你的修改并没有让它变得更好,反而更糟了。"循环+Thread.Sleep"之所以邪恶是有很多原因的。此外,你的解决方案可能不兼容发布版本,因为编译器可能会删除与常量值的比较...每次提倡"循环+Thread.Sleep"都会导致一只小猫死亡。 - user57508
@AndreasNiedermair -- 我提供的解决方案只是一个临时措施,但我的总体建议是去使用TAP。 - rory.ap
1
感谢您的讨论。 我使用了WaitHandle来解决我的问题。 - j0h4nn3s
1
@Default 不是的,Monitor.Wait 会释放等待,以便任务中的锁可以获得并激活它。请参见 http://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystified:*如果线程A持有关键对象上的锁定,为什么当线程B尝试获取锁定时不会阻塞?当然,这是被正确处理的。线程A中的Wait调用在等待之前释放锁。这允许线程B获取锁并调用Pulse。...* - user57508
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接