Skip to main content
 首页 » 编程设计

c#之WPF 表单与后台线程锁定

2024年04月18日38Leo_wl

我正在开发一个使用 .NET 4.5 的 ZipArchive 和 ZipFile 类的内部备份程序。到目前为止,我已经成功地压缩了不同大小和内容的文件夹,但是当压缩一个大文件夹时,用户界面会卡住,直到所有备份完成。我一直在谷歌和 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。这可能反过来要求 that 方法的调用者被标记为 async,等等。 async 就是这样。