代码之家  ›  专栏  ›  技术社区  ›  SuperJMN

如何处理同步(阻止)调用,使UI无响应

  •  1
  • SuperJMN  · 技术社区  · 6 年前

    考虑到这段代码,我注意到我的UI被阻塞了一段时间(Windows甚至弹出一条消息说应用程序没有响应)。

    using (var zip = await downloader.DownloadAsZipArchive(downloadUrl))
    {
        var temp = FileUtils.GetTempDirectoryName();
        zip.ExtractToDirectory(temp);   // BLOCKING CALL
    
        if (Directory.Exists(folderPath))
        {
            Directory.Delete(folderPath, true);
        }
    
        var firstChild = Path.Combine(temp, folderName);
        Directory.Move(firstChild, folderPath);
        Directory.Delete(temp);
    }
    

    经过一些检查,我发现这条线写着:

    zip.ExtractToDirectory(temp);
    

    是罪魁祸首。

    我认为将其转化为足以使其发挥作用:

    await Task.Run(() => zip.ExtractToDirectory(temp));
    

    但这是解决这个问题的好办法吗?

    我的背景是 System.Reactive (我都在研究反应式编程)我想知道是否有更优雅的方法来处理这个问题。

    3 回复  |  直到 6 年前
        1
  •  3
  •   Enigmativity    6 年前

    在RX做这个有点讨厌。结合一 Task<IDisposable> 是粗糙的。这就是我得到的:

    Observable
        .FromAsync(() => downloader.DownloadAsZipArchive(downloadUrl))
        .SelectMany(z =>
            Observable
                .Using(() => z, zip => Observable.Start(() =>
                {
                    var temp = FileUtils.GetTempDirectoryName();
                    zip.ExtractToDirectory(temp);   // BLOCKING CALL
    
                    if (Directory.Exists(folderPath))
                    {
                        Directory.Delete(folderPath, true);
                    }
    
                    var firstChild = Path.Combine(temp, folderName);
                    Directory.Move(firstChild, folderPath);
                    Directory.Delete(temp);             
                })))
        .Subscribe();
    
        2
  •  3
  •   SuperJMN    6 年前

    是的,你可以想象 ExtractToDirectory 会花时间的,可惜没有 async 此方法的版本作为它的CPU绑定工作负荷。

    你能做的(有争议的是),是将它卸载到线程池,但是你会招致线程池线程惩罚,这意味着你会占用一个线程池线程并阻塞它(耗尽宝贵的资源)。然而,因为 Task 等待,它将释放UI上下文。

    await Task.Run(() => zip.ExtractToDirectory(temp));
    

    注意,虽然这可以解决问题,但这里的最佳方法是使用 TaskCompletionSource 这基本上是任务的事件(因为缺少更好的单词),它将节省不必要的捆绑线程。

    更新 伟大的评论 olitee

    稍微少一点争议…您可以将此扩展到使用:

    await Task.Factory.StartNew(() => zip.ExtractToDirectory(temp), TaskCreationOptions.LongRunning); 
    

    这将迫使 新的操作专用线程。虽然会有一个 创建该线程而不是回收 合并一个-但对于这样一个长期运行的操作来说,这不是一个问题。

        3
  •  0
  •   JohanP    6 年前

    我很可能将zip提取和目录创建代码重构为它自己的方法。这将使以后更容易卸载到线程。它还具有让调用者决定是否要在另一个线程上运行的额外好处。

    public void ExtractZip(ZipFile zip)
    {
       var temp = FileUtils.GetTempDirectoryName();
       zip.ExtractToDirectory(temp);   // BLOCKING CALL
    
       if (Directory.Exists(folderPath))
       {
           Directory.Delete(folderPath, true);
       }
    
       var firstChild = Path.Combine(temp, folderName);
       Directory.Move(firstChild, folderPath);
       Directory.Delete(temp);
    }
    

    然后让顶级方法下载文件并提取zip

    // this method contains async IO code aswell as CPU bound code
    // that has been offloaded to another thread
    public async Task ProcessAsync()
    {
       using (var zip = await downloader.DownloadAsZipArchive(downloadUrl))
       {
          // I would use Task.Run until it proves to be a performance bottleneck
          await Task.Run(() => ExtractZip(zip));
       }
    }