【问题标题】:WPF form locking up with background threadWPF 表单与后台线程锁定
【发布时间】:2016-06-27 21:12:39
【问题描述】:

我正在开发一个内部备份程序,该程序利用 .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 在后台压缩时保持交互性的其他几件事。

有什么建议吗?谢谢。

【问题讨论】:

  • result.Result,永远不要组合 asnyc/await 和 .Result.Wait()。此外,我在您的异步函数中看到的唯一 awaitawait Task.FromResult(true); 或 `await Task.FromResult(false);`,这对您没有任何作用。没有代码在后台线程上运行。同样Dispatcher.Invoke 将代码放在 UI 线程上,它只是等待一段时间再将其放在那里。
  • 感谢斯科特的信息。我过去用 WinForms 完成了线程,但 WPF 有点令人困惑。明天我要再试一次;我的大脑厌倦了今天看代码。
  • 摆脱所有 async/await 的东西,摆脱 dispacter,然后只需执行 var result = await Task.Run(() =&gt; BackupFolder(b1.Tag.ToString(), b1.Name); 并标记调用函数 async

标签: c# wpf multithreading .net-4.5


【解决方案1】:

您的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 就是这样。

【讨论】:

  • 感谢这很好。我发现它比实际复杂得多。
【解决方案2】:

首先,Dispatcher.Invoke(...);InvokeAsync 不会在后台线程中运行任何东西。完全相反的是 Dispatcher 类的目的 - 在 UI 线程上安排工作。

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

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;   
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-22
    • 2010-12-03
    • 1970-01-01
    相关资源
    最近更新 更多