【问题标题】:Wait for download to complete in BackgroundWorker在 BackgroundWorker 中等待下载完成
【发布时间】:2015-01-16 13:47:03
【问题描述】:

我有一个带有进度条的对话框。显示对话框时,Backgroundworker 应下载两个文件(使用 WebClient)并自动将它们复制到指定位置。 如何在复制新文件之前等待文件下载。

我试图用await 做一些事情,但我无法将Backgroundworker 更改为异步方法。如何在工作器中等待下载完成?

运行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();
}

【问题讨论】:

    标签: c# asynchronous backgroundworker webclient


    【解决方案1】:

    您可以使用同步的WebClient 方法(例如,DownloadFile 代替DownloadFileTaskAsync),或者您可以直接使用async/await 直接代替 @987654328 @。在这种情况下,您主要进行 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();
    }
    

    【讨论】:

    • 如果我错了请告诉我,但我认为我只能在异步方法中使用await。由于fmUpdateingDatabaseDialog_Shown 不是异步的,我不知道如何实现它。编辑:我不同步 WebClient,因为看到进度很重要。
    • 请问您对async void 的看法是什么? I read an article that it should be avoided 我已经看到你在这里回答了很多任务问题 :) 我认为你对此事的看法可能很有趣。
    • 你应该避免async voidasync void 应该只用于事件处理程序(或逻辑事件处理程序,例如ICommand.Execute)。在这种情况下,fmUpdateingDatabaseDialog_Shown 是一个事件处理程序,所以async void 是可以接受的。 DownloadAsync 不是事件处理程序,因此 async void 对于该方法是错误的。我有an article 详细说明。
    【解决方案2】:

    您可以使用类似的东西,它使用"wait and pulse" 机制来延迟代码,直到您的下载操作完成:

    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);
    }
    

    但是,如果您有资源,我建议您重构代码以完全使用异步等待方法(从而避免后台工作人员)。后台工作器是传统的异步方法之一,而推荐的方法是 TAP

    有关如何执行此操作的示例,请参见 Stephen Cleary 的回答。

    【讨论】:

    • -,永远不要不建议使用 Thread.Sleep(任何类型,无论是 0 还是 1000)和这样的场景的循环,而是选择 WaitHandles! (查看我的全部原因/如何/...@stackoverflow.com/questions/8815895/…
    • 我想到了,但对我来说似乎有点脏。你有异步等待方法的资源吗?我不知道如何从 Dialog_shown 事件启动异步函数
    • @roryap 您的编辑并没有让它变得更好,甚至更糟。 "loop + Thread.Sleep" 之所以邪恶,有很多原因。此外,您的解决方案可能与发布构建不兼容,因为编译器可能会删除您与常量值的比较......每次推广“循环+Thread.Sleep”时,一只小猫就会死去。
    • 感谢您的讨论。我使用 WaitHandle 来解决我的问题。
    • @Default nope,Monitor.Wait 释放等待,以便任务中的lock 可以获取并脉冲它。请看codeproject.com/Articles/28785/…如果线程A持有key对象上的锁,为什么线程B在尝试获取锁时没有阻塞?当然,这是妥善处理的。线程 A 中对 Wait 的调用在等待之前释放了锁。这允许线程 B 获取锁并调用 Pulse。 ...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-28
    • 1970-01-01
    • 1970-01-01
    • 2012-12-24
    相关资源
    最近更新 更多