如何在不同的线程上使用静态资源?

Posted

技术标签:

【中文标题】如何在不同的线程上使用静态资源?【英文标题】:How can I use static resources on different threads? 【发布时间】:2021-10-11 22:38:24 【问题描述】:

我有一个 WPF 应用程序,在 UI 线程上有一个长时间运行的任务,我想为其显示一个进度条 (*)。由于 UI 很忙,遵循this 解决方案,我选择在单独的 UI/STA-Thread 上打开带有进度条的窗口。

一切正常 - 我第一次创建窗口。问题显然是窗口使用Style="StaticResource CustomWindowStyle" 并且样式的实例是静态的,即“缓存”并在使用此样式的所有实例之间共享。

但是,这个实例(作为所有/大多数 UI 元素)是一个 DispatcherObject,只能在最初创建它的线程中使用。因此,当我第二次打开一个窗口(在它自己的新 UI 线程上)时,它会访问之前在不同线程上构建的相同静态 Style 资源,我得到以下异常:

System.Windows.Markup.XamlParseException
  HResult=0x80131501
  Message='Set property 'System.Windows.FrameworkElement.Style' threw an exception.' Line number '15' and line position '9'.
  Source=PresentationFramework
  StackTrace:
   at System.Windows.Markup.XamlReader.RewrapException(Exception e, IXamlLineInfo lineInfo, Uri baseUri)

  This exception was originally thrown at this call stack:
    System.Windows.StyleHelper.ProcessInstanceValuesHelper(ref MS.Utility.ItemStructList<System.Windows.ChildValueLookup>, System.Windows.DependencyObject, int, System.Collections.Specialized.HybridDictionary, bool)
    System.Windows.StyleHelper.ProcessInstanceValuesForChild(System.Windows.DependencyObject, System.Windows.DependencyObject, int, System.Collections.Specialized.HybridDictionary, bool, ref MS.Utility.FrugalStructList<System.Windows.ChildRecord>)
    System.Windows.StyleHelper.DoStyleInvalidations(System.Windows.FrameworkElement, System.Windows.FrameworkContentElement, System.Windows.Style, System.Windows.Style)
    System.Windows.StyleHelper.UpdateStyleCache(System.Windows.FrameworkElement, System.Windows.FrameworkContentElement, System.Windows.Style, System.Windows.Style, ref System.Windows.Style)
    System.Windows.FrameworkElement.OnStyleChanged(System.Windows.DependencyObject, System.Windows.DependencyPropertyChangedEventArgs)
    System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs)
    System.Windows.FrameworkElement.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs)
    System.Windows.DependencyObject.NotifyPropertyChange(System.Windows.DependencyPropertyChangedEventArgs)
    System.Windows.DependencyObject.UpdateEffectiveValue(System.Windows.EntryIndex, System.Windows.DependencyProperty, System.Windows.PropertyMetadata, System.Windows.EffectiveValueEntry, ref System.Windows.EffectiveValueEntry, bool, bool, System.Windows.OperationType)
    System.Windows.DependencyObject.SetValueCommon(System.Windows.DependencyProperty, object, System.Windows.PropertyMetadata, bool, bool, System.Windows.OperationType, bool)
    ...
    [Call Stack Truncated]

Inner Exception 1:
InvalidOperationException: Cannot access Freezable 'System.Windows.Shell.WindowChrome' across threads because it cannot be frozen.

如果我删除 Style 属性,一切都很好。

我也尝试使用DynamicResource,但引用的样式引用了我无法控制的其他静态资源,并导致“进一步”出现同样的问题。

这可以解决吗?

(*) 是的,我知道:长时间运行的操作不应该由 UI 线程处理,但是改变它需要太多的重构(目前)并且用户在这种情况发生时不要做任何事情,所以即使,如果操作外包给一个任务,我实际上会禁用 UI。

编辑 1: 所以带我去this post;我尝试在窗口声明中添加x:Shared="False",但没有帮助。

编辑 2:我还尝试按照here 的描述冻结样式资源,但没有帮助。

【问题讨论】:

【参考方案1】:

如here 所述,解决方案是为引用的样式设置x:Shared="True",而不是窗口。

幸运的是,就我而言,我确实可以控制此样式定义(但不能控制该样式使用的其他资源),因此我可以修改它的定义。

由于我非常希望不必修改样式,因此我还尝试在 Window.Resources 中定义本地样式,如下所示:

<Window.Resources>
    <Style x:Key="CustomStyle" TargetType="x:Type Window" BasedOn="StaticResource CustomWindowStyle" x:Shared="False"/>
</Window.Resources>

然后我尝试改用它。首先,我必须通过代码分配样式,因为我不知道如何在窗口的属性中使用样式,该属性(稍后)被定义为窗口自己的资源之一:

this.Style = this.Resources["CustomStyle"] as Style;

但是,遗憾的是,失败了,出现以下异常(就在那一行):

System.InvalidOperationException
  HResult=0x80131509
  Message=The calling thread cannot access this object because a different thread owns it.
  Source=WindowsBase
  StackTrace:
   at System.Windows.Threading.Dispatcher.VerifyAccess()
   at System.Windows.Freezable.System.Windows.ISealable.get_IsSealed()
   at System.Windows.StyleHelper.SealIfSealable(Object value)
   at System.Windows.Setter.Seal()
   at System.Windows.SetterBaseCollection.Seal()
   at System.Windows.Style.ProcessSetters(Style style)
   at System.Windows.Style.ProcessSetters(Style style)
   at System.Windows.Style.Seal()
   at System.Windows.StyleHelper.UpdateStyleCache(FrameworkElement fe, FrameworkContentElement fce, Style oldStyle, Style newStyle, Style& styleCache)
   at System.Windows.FrameworkElement.OnStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
   at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
   at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
   at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
   at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
   at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
   at ...PopupWindow..ctor()...
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

  This exception was originally thrown at this call stack:
    System.Windows.Threading.Dispatcher.VerifyAccess()
    System.Windows.Freezable.System.Windows.ISealable.IsSealed.get()
    System.Windows.StyleHelper.SealIfSealable(object)
    System.Windows.Setter.Seal()
    System.Windows.SetterBaseCollection.Seal()
    System.Windows.Style.ProcessSetters(System.Windows.Style)
    System.Windows.Style.ProcessSetters(System.Windows.Style)
    System.Windows.Style.Seal()
    System.Windows.StyleHelper.UpdateStyleCache(System.Windows.FrameworkElement, System.Windows.FrameworkContentElement, System.Windows.Style, System.Windows.Style, ref System.Windows.Style)
    System.Windows.FrameworkElement.OnStyleChanged(System.Windows.DependencyObject, System.Windows.DependencyPropertyChangedEventArgs)
    ...
    [Call Stack Truncated]
    

所以,现在,我使用的是修改后的静态样式:

<Style x:Key="CustomWindowStyle" TargetType="x:Type Window" x:Shared="False">
    ...
</Style>

【讨论】:

如果您在 UI 线程上有一个长时间运行的任务,让该任务异步并让它在后台线程上工作不是更好吗?似乎比创建多个 UI 线程要简单得多。

以上是关于如何在不同的线程上使用静态资源?的主要内容,如果未能解决你的问题,请参考以下文章

多线程应用中静态全局非托管资源的管理

前段性能-静态资源,资源压缩

高并发解决方案与并发编程,线程池,缓存,消息队列,分库分表java系统架构

如何使用 Serverless Devs 部署静态网站到函数计算(上)

Tomcat如何快速响应静态资源(DefaultServlet+浏览器缓存)

Tomcat如何快速响应静态资源(DefaultServlet+浏览器缓存)