如何处理同步(阻塞)调用使得UI反应迟钝

问题描述 投票:1回答:3

鉴于这一段代码,我发现我的UI封锁了一段时间(即使视窗弹出一条消息说该应用程序不响应。

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背景(我都在与反应式编程),我想知道是否有处理这个更优雅的方式。

c# .net async-await system.reactive
3个回答
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();

3
投票

是的,你可以想像ExtractToDirectory需要时间,遗憾的是这种方法,它是一个CPU密集型工作负载的无async版本。

你能做什么(有争议的),它是卸载到线程池,你会招致一个线程池线程点球,虽然,这意味着你采取一个线程池线程,(使用了宝贵的资源)阻止它。然而,由于Task等待,这将腾出的UI环境。

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

请注意,虽然这将解决这个问题,在这里最好的方法是使用一个TaskCompletionSource这基本上是任务事件(缺乏更好的话),这将节省不必要占用一个线程

更新通过olitee大评论

略少有争议的......你可以扩展这种使用方法:

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

这将迫使新的专用线程操作的创建。尽管会有用于创建线程,而不是再造一个集中的一个额外的处罚 - 但是这不是一个问题像这样的长时间运行的操作。


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));
   }
}
© www.soinside.com 2019 - 2024. All rights reserved.