鉴于这段代码,我注意到我的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
的背景知识(我参与了反应式编程),我想知道是否有一种更优雅的方式来解决这个问题。
答案 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));
}
}