【问题标题】:Access UI thread while app is being suspended?在应用程序暂停时访问 UI 线程?
【发布时间】:2017-12-12 10:14:05
【问题描述】:

我有一个允许用户创建和修改文本文档的 UWP 应用。我很难让保存机制与应用挂起/恢复生命周期一起工作。

这是我所拥有的:

  1. 所有磁盘访问都在单独的后台线程上完成,以保持 UI 响应
  2. 该后台线程还序列化所有磁盘访问,以确保始终只发生一次读取或写入操作
  3. 在保存文档之前和之后,UI 会更新以指示正在进行保存。这是通过Dispatcher.RunAsync() 在 UI 线程上安排的

应用暂停时:

  • 在暂停时,我必须最后一次保存文档以确保所有更改都在磁盘上(应用程序在暂停时可能会终止)
  • 所以我请求ExtendedExecutionSession
  • 我在队列中安排了最后一次保存操作
  • 然后我等待队列处理所有挂起的磁盘访问操作
  • 最后,我将扩展执行会话标记为已完成

我的问题:

  • 已安排好的保存操作完成其磁盘访问,然后尝试通过Dispatcher.RunAsync() 更新主线程上的 UI。后台队列等待此任务完成,但它永远不会完成,因为到那时,UI 线程已经停止

→ 所以我的最终保存操作永远不会执行,因为后台队列一直在等待更新 UI。

这是流程图:

出现的几个问题:

  1. 当应用程序暂停时,UI 线程究竟何时停止?只有当它空闲或“介于两者之间”时? IE。如果我通过Dispatcher.RunAsync() 安排了一个区块,这至少会完成还是“冻结”?
  2. 有没有办法让我检查当前窗口的 UI 线程是否已经停止,以便我知道我不能再在后台文件访问线程中访问它?
  3. 应用暂停时,哪些线程被停止,何时停止?
  4. 当应用暂停时,我的后台文件访问线程是否也有被停止的风险?

总结问题:

当应用程序暂停时,我必须确保等待后台线程上可能挂起的磁盘访问完成,然后再最终保存文档一次,以防我的应用程序稍后终止。

【问题讨论】:

    标签: c# multithreading uwp windows-10-universal suspend


    【解决方案1】:

    这听起来太熟悉了!

    我从您的问题中了解到,保存操作以更新 UI 结束,但由于它在后台线程中运行,除非您使用 Dispatcher 安排它,否则它无法触摸 UI。只是为了形象化,你的代码看起来像这样吗:

    public async Task SaveAsync()
    {
       // .. save to disk ..
    
       await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
       {
          // update UI here
       }
    
       // Save Complete!
    }
    

    如果是这样,问题在于 UI 更新是排队后台操作的一部分,如果 UI 更新或 Dispatcher 本身阻止了任何操作,则在到达 //Save Complete! 行之前不会开始其他操作,如果 Dispatcher 从未执行您的操作,则永远不会发生。

    不要将保存操作与 UI 更新相关联,而是尝试公开将触发 UI 更新的事件。例如:

    public event EventHandler SaveStarted;
    public event EventHandler SaveCompleted;
    
    public async Task SaveAsync()
    {
       SaveStarted?.Invoke(this, EventArgs.Empty);
    
       // .. save to disk ..
    
       SaveCompleted?.Invoke(this, EventArgs.Empty);
    }
    

    您的视图可以将处理程序附加到这些事件并显示/隐藏适当的视觉效果,而您的挂起处理程序可以安全地调用 SaveAsync() 方法而无需担心 UI。

    当视图为Unloaded 时,请确保分离这些事件处理程序。引发这些事件时,您的保存操作不会阻塞队列。


    更新 1

    是的,您的事件处理程序必须使用Dispatcher 在 UI 线程上运行:

    public void OnSaveCompleted(object sender, EventArgs e)
    {
       Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
       {
          // update UI here...
       }
    }
    

    因为处理程序被强制返回void,所以不会阻塞后台线程。


    更新 2

    听起来你有一些紧密的耦合。您需要重新设计您的应用程序,以便您的文本文档逻辑位于一个单独的项目中。我总是在 .Net Standard 项目中编写代码,该项目会强制所有 UI 退出。该项目将不允许任何 UI 代码,从而保持您的逻辑干净和解耦。

    如果您的所有代码都位于 UWP 项目中,那么很容易开始包括视图逻辑、XAML 控件以及在您的情况下,在您的应用程序中与演示无关的部分中的 Dispatcher

    在您的情况下,您需要弄清楚如何使您的文本文档逻辑成为文档状态的所有者,而不是 UI。 hasChanges 只能由拥有 SaveAsync() 的相同逻辑修改。如果在 UI 操作过程中出现中断/阻塞/失败/等,并且它从未成功进入文本文档,那么这是一个不完整的操作,不应该是文档的一部分,也不应该是文档的责任担心关于它何时暂停并保存到磁盘。

    如果 UWP 在其中一项操作期间因任何原因终止您的应用,那么它就消失了。您的下一个任务是弄清楚当应用再次唤醒时如何恢复此操作。

    【讨论】:

    • 感谢您的回答!几个问题:视图将在哪个线程上接收事件?如果我从后台工作线程调用事件,视图不会在同一个后台线程上接收事件吗?处理事件时我需要另一个Dispatcher.RunAsync() 吗?这不会再次阻塞我的后台队列吗?
    • 还有一件事:在我目前的设计中,我不能使用事件,因为我使用 UI 线程来同步对共享状态的访问(例如 hasChanges 标志)。在我的后台操作可以​​继续之前,我需要等待 UI 线程上运行的块完成。整个问题是由于应用程序暂停时 UI 线程停止引起的......我希望那里有一些更真实的示例代码......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    • 2012-11-11
    相关资源
    最近更新 更多