WPF表单与后台线程锁定



我正在开发一个内部备份程序,该程序利用.NET 4.5的ZipArchiveZipFile类。到目前为止,我已经成功地压缩了不同大小和内容的文件夹,但当压缩一个大文件夹时,UI会冻结,直到所有备份完成。我一直在谷歌和SO上寻找解决方案,但到目前为止运气不佳。

这是我启动后台线程的调用:

Dispatcher.Invoke(() =>
    {
        var result = BackupFolder(b1.Tag.ToString(), b1.Name);
        Log = result.Result ? new BackupLog(string.Format("Finished zipping '{0}.zip'", b1.Name)) : new BackupLog(string.Format("Error zipping '{0}.zip'", b1.Name));
    }, DispatcherPriority.Background);

这是它调用的方法:

private async Task<bool> BackupFolder(string path, string folderName)
{
    try
    {
        var zipPath = string.Format("{0}{1}.zip", _backupPath.Backslash(), folderName);
        Log = new BackupLog(string.Format("Starting zipping '{0}.zip'", folderName));
        if (File.Exists(zipPath)) // ZIP already exists, let's update the contents
        {
            using (var existingZip = new FileStream(zipPath, FileMode.Open))
            {
                using (var archive = new ZipArchive(existingZip, ZipArchiveMode.Update))
                {
                    foreach (var file in Directory.GetFiles(path))
                    {
                        if (archive.Entries.Any(a => a.FullName.Equals(Path.GetFileName(file)))) // Check if current file exists in the ZIP
                        {
                            var zipArchiveEntry =
                                archive.Entries.First(a => a.FullName.Equals(Path.GetFileName(file)));
                            if (zipArchiveEntry != null) zipArchiveEntry.Delete(); // Remove existing file from ZIP
                        }
                        archive.CreateEntryFromFile(file, Path.GetFileName(file));
                    }
                }
            }
        }
        else // ZIP does not exist, let's create it
        {
            ZipFile.CreateFromDirectory(path, zipPath);
        }
        return await Task.FromResult(true);
    }
    catch (Exception ex)
    {
        Debugger.Break();       
    }
    return await Task.FromResult(false);
}

我尝试过使用Task.Run(),创建BackgroundWorker,可能还有其他一些我忘记了的事情,试图让UI在后台快速移动时保持交互式。

有什么建议吗?谢谢

您的BackupFolder()方法是"AINO":async仅在名称上。async关键字的存在是为了更改方法的语义,使其可以使用await关键字。您唯一使用await的地方是,您正在"等待"一个初始化处于完成状态的Task对象。没有实际的异步等待。

同时,您正在使用Dispatcher.Invoke()来执行该方法。这意味着您明确指示框架在UI线程中执行您的方法。这一点,再加上BackupFolder()方法实际上不是异步的,意味着在操作期间会阻塞UI线程。

要修复代码,请将BackupFolder()改回简单的同步方法:

private bool BackupFolder(string path, string folderName)
{
    .
    .
    .
        return true;
    }
    catch (Exception ex)
    {
        Debugger.Break();       
    }
    return false;
}

然后,不调用Dispatcher.Invoke(),只需在自己的Task:中运行同步BackupFolder()方法

string path = b1.Tag.ToString(), folderName = b1.Name;
bool result = await Task.Run(() => BackupFolder(path, folderName));

注:

  • 我在上面是保守的,并假设b1对象,无论它是什么,都是而不是线程安全的。因此,我添加了用于从b1对象捕获当前值的局部变量,这样,当BackupFolder()方法最终在任务线程中执行时,这些值可以安全地传递给它
  • 当然,在这里使用await运算符需要将包含该调用的方法标记为async。这可能反过来要求方法的调用者标记为async,依此类推。async就是这样

首先,Dispatcher.Invoke(...);InvokeAsync不会在后台线程中运行任何内容。Dispatcher类的目的恰恰相反——调度UI线程上的工作。

其次,您错误地使用了async/await。只有任务内部的内容才会在后台线程中运行:

await DoSomething();
async Task DoSomething(){
   Trace.WriteLine("This is still in UI thread")
   await Task.Run(...); //action inside task will run asynchronously
   Trace.WriteLine("This is UI thread again");
}

所以你的代码可以看起来像这样:

//this is UI thread
bool result = await Task.Run(() => BackupFolder(b1.Tag.ToString(), b1.Name"));
//this is UI thread again
private bool BackupFolder(string path, string folderName){
   //this is background thread, since it is invoked from Task;   
}

相关内容

  • 没有找到相关文章

最新更新