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

时间:2019-02-06 23:50:07

标签: c# .net async-await system.reactive

鉴于这段代码,我注意到我的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 个答案:

答案 0 :(得分:3)

是的,您可以想象ExtractToDirectory将花费一些时间,不幸的是,此方法没有async版本,因为它受CPU限制。

您可以做的(有争议的)是将其卸载到线程池中,但是您将招致线程池线程罚款,这意味着您要占用线程池线程并阻塞它(消耗宝贵的资源)。但是,由于等待Task,所以它将释放UI上下文。

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

请注意,尽管这可以解决问题,但是最好的方法是使用TaskCompletionSource,这基本上是Task的事件(因为缺少更好的单词),它将节省不必要地占用线程的时间< / p>

更新 olitee的好评论

  

争议较少...您可以将其扩展为使用:

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

这将强制创建一个   新的专用操作线程。虽然会有一个   创建该线程而不是回收一个额外的罚款   合并一个-但这对于长时间运行的操作来说不是一个问题。

答案 1 :(得分:3)

在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 :(得分:0)

我很可能会将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));
   }
}