【问题标题】:Background STA thread with custom window style using window chrome throwing cross threaded access exception具有自定义窗口样式的后台 STA 线程使用窗口镶边引发跨线程访问异常
【发布时间】:2017-11-30 14:54:18
【问题描述】:

这个有点奇怪。 从 wpf 的主窗口(单击按钮),我正在创建另一个 STA 线程,其中我正在显示一个自定义窗口。此自定义窗口应用了一种使用 shell 中的 WindowChrome 类的样式。 调用 Show() 方法时出现异常。

无法跨平台访问 Freezable 'System.Windows.Shell.WindowChrome' 线程,因为它不能被冻结。

如果我删除 WindowChrome 设置器,一切正常。 我错过了什么?

我已经尝试将窗口镶边标记为冻结,但徒劳无功!

来源的副本是可用的here

更新: 忘了提到在样式上添加 x:Shared="False" 似乎可以解决问题,但我不知道为什么!这会导致任何性能瓶颈吗?

MainWindow.xaml:

<Window x:Class="WpfApplication7.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication7"
        Title="MainWindow"
        Height="350"
        Width="525" Style="{StaticResource ResourceKey=WindowStyle}">
    <Grid>
        <Button Content="Open another window please..."
                Click="Button_Click" />
    </Grid>
</Window>

MainWindow.xaml.cs:

using System.Threading;
using System.Windows;

namespace WpfApplication7
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Thread windowThread = new Thread(new ThreadStart(() =>
            {
                Window customWindow = new BackgroundWindow();
                customWindow.Closed += (s, a) => System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Background);
                customWindow.Show();
                System.Windows.Threading.Dispatcher.Run();
            }));
            windowThread.IsBackground = true;
            windowThread.SetApartmentState(ApartmentState.STA);
            windowThread.Start();
        }
    }
}

BackgroundWindow.xaml:

<Window x:Class="WpfApplication7.BackgroundWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication7"
        Title="BackgroundWindow"
        WindowStartupLocation="CenterScreen" 
        Style="{StaticResource ResourceKey=WindowStyle}">
</Window>

WindowStyle.xaml(与 App.xaml 合并

 <!-- Setting x:Shared=False will solve the cross threaded exception -->
<Style x:Key="WindowStyle"
       TargetType="{x:Type Window}">
    <Setter Property="Padding"
            Value="5,5,5,5" />
    <Setter Property="BorderBrush"
            Value="Black" />
    <Setter Property="BorderThickness"
            Value="1" />
    <Setter Property="WindowChrome.WindowChrome">
        <Setter.Value>
            <WindowChrome CaptionHeight="44"
                          GlassFrameThickness="-1"
                          CornerRadius="0,0,0,0" />
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Window}">
                <Border Background="{TemplateBinding Property=Background}"
                        BorderBrush="{TemplateBinding Property=BorderBrush}"
                        BorderThickness="{TemplateBinding Property=BorderThickness}">
                    <Grid Background="{TemplateBinding Property=Background}"
                          UseLayoutRounding="True"
                          SnapsToDevicePixels="True">
                        <Grid.RowDefinitions>
                            <!-- Window Controls -->
                            <RowDefinition Height="44" />
                            <!-- Content -->
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <DockPanel x:Name="PART_DragPanel"
                                   Grid.Row="0"
                                   Background="Black">
                            <Button x:Name="PART_CloseButton"
                                    DockPanel.Dock="Right"
                                    HorizontalAlignment="Right"
                                    Margin="3,8,8,8"
                                    WindowChrome.IsHitTestVisibleInChrome="True"
                                    Width="20"
                                    Height="20" />
                            <Button x:Name="PART_RestoreButton"
                                    DockPanel.Dock="Right"
                                    HorizontalAlignment="Right"
                                    Margin="3,8,3,8"
                                    Visibility="Collapsed"
                                    WindowChrome.IsHitTestVisibleInChrome="True"
                                    Width="20"
                                    Height="20" />
                            <Button x:Name="PART_MinimizeButton"
                                    DockPanel.Dock="Right"
                                    HorizontalAlignment="Right"
                                    Margin="3,8,3,8"
                                    Visibility="Collapsed"
                                    WindowChrome.IsHitTestVisibleInChrome="True"
                                    Width="20"
                                    Height="20" />
                            <TextBlock x:Name="PART_Title"
                                       DockPanel.Dock="Left"
                                       Margin="8,8,8,8"
                                       Text="{TemplateBinding Property=Title}"
                                       IsHitTestVisible="False"
                                       WindowChrome.IsHitTestVisibleInChrome="True" />
                        </DockPanel>
                        <Border x:Name="contentBorder"
                                Grid.Row="1"
                                Padding="{TemplateBinding Property=Padding}">
                            <AdornerDecorator>
                                <ContentPresenter />
                            </AdornerDecorator>
                        </Border>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="ResizeMode"
                             Value="CanMinimize">
                        <Setter TargetName="PART_MinimizeButton"
                                Property="Visibility"
                                Value="Visible" />
                    </Trigger>
                    <Trigger Property="ResizeMode"
                             Value="NoResize">
                        <Setter TargetName="PART_RestoreButton"
                                Property="Visibility"
                                Value="Collapsed" />
                        <Setter TargetName="PART_MinimizeButton"
                                Property="Visibility"
                                Value="Collapsed" />
                    </Trigger>
                    <Trigger Property="ResizeMode"
                             Value="CanResize">
                        <Setter TargetName="PART_RestoreButton"
                                Property="Visibility"
                                Value="Visible" />
                        <Setter TargetName="PART_MinimizeButton"
                                Property="Visibility"
                                Value="Visible" />
                    </Trigger>
                    <Trigger Property="ResizeMode"
                             Value="CanResizeWithGrip">
                        <Setter TargetName="PART_RestoreButton"
                                Property="Visibility"
                                Value="Visible" />
                        <Setter TargetName="PART_MinimizeButton"
                                Property="Visibility"
                                Value="Visible" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="ResizeMode"
                 Value="CanResize">
            <Setter Property="WindowChrome.ResizeBorderThickness"
                    Value="5,5,5,5" />
        </Trigger>
        <Trigger Property="ResizeMode"
                 Value="CanResizeWithGrip">
            <Setter Property="WindowChrome.ResizeBorderThickness"
                    Value="5,5,5,5" />
        </Trigger>
        <Trigger Property="ResizeMode"
                 Value="NoResize">
            <Setter Property="WindowChrome.ResizeBorderThickness"
                    Value="0,0,0,0" />
        </Trigger>
    </Style.Triggers>
</Style>

【问题讨论】:

  • 不要创建多个 UI 线程。你只会给自己造成一个伤害的世界。使用单个 UI 线程。
  • Dispatcher.CurrentDispatcher 不是应用程序的调度程序,也不是您要关闭的调度程序。因此,当需要进行清理时,这种情况就会发生。请改用 Application.Current.Dispatcher。
  • 我无法使用您提供的代码复制它。
  • @Evk 你能看到带有 WindowStyle 的背景窗口吗?如果您无法重现,那么问题一定是零星的,因为我能够。不过,我的环境有一个小的变化。 WindowStyles.xaml 被添加到另一个 Resources.xaml,后者被合并到 App.xaml 中。我也在使用.Net 4.6。
  • 是的,我也使用 .NET 4.6,我已将样式添加到资源并合并到应用程序,但(如预期的那样)没有任何区别。我每次都单击按钮并查看背景窗口,并应用了样式。也许您可以在某处发布完整的可重现项目。

标签: c# wpf multithreading xaml window-chrome


【解决方案1】:

问题的根源是当你这样做时:

<Setter Property="WindowChrome.WindowChrome">
    <Setter.Value>
        <WindowChrome CaptionHeight="44"
                      GlassFrameThickness="-1"
                      CornerRadius="0,0,0,0" />
    </Setter.Value>
</Setter>

值(在本例中为WindowChrome 实例)创建一次并在构造时保留样式。每次将样式应用于控件时,它都不会创建新实例。首先,您将样式应用于主窗口。此时样式是从 xaml 创建的,因此创建了 WindowChrome 实例。当您从另一个线程将样式应用于背景窗口时 - 不会重新创建样式,而是使用已创建的 Style 实例。值得注意的是,样式设置器包含在另一个线程中创建的值,该值是可冻结但未冻结的 - 因此它会因异常而失败,因为它被认为是危险的(并且无论如何都不会工作,因为您无法分配在另一个线程中创建的这个 WindowChrome 实例到你的BackgroundWindow,你也不能克隆它)。

当您将x:Shared=False 应用于样式时 - 在每个请求上都会创建Style 的新实例,从而避免了该问题(因为也为每个样式实例创建了新的 WindowChrome 实例)。另一种强制新实例的方法是:

<WindowChrome x:Key="chrome" x:Shared="False"
              CaptionHeight="44"
              GlassFrameThickness="-1"
              CornerRadius="0,0,0,0" />

<Style ...>
    <Setter Property="WindowChrome.WindowChrome" Value="{DynamicResource chrome}"/>
</Style>

只有在没有其他办法的情况下才使用多个 UI 线程,因为它们可能很棘手。

【讨论】:

  • 我同意你的推论。 1. 由于 WindowChrome 是可冻结的,我尝试设置 po:Freeze="True" 而不设置 x:Shared="True" 但它似乎不起作用。这是否意味着 WindorChrome,即使设置为冻结也不会冻结? 2. 设置 x:Shared="False" 对性能/内存占用有影响吗?
  • 1.并非每个可冻结对象都可以一直冻结。它有 CanFreeze 属性表明这一点。例如,如果您在代码中创建 WindowChrome 并检查 CanFreeze 属性 - 它将返回 false。
  • 2.设置 Shared="False" 会使对指定资源的每个请求都创建该资源的新副本,所以是的 - 它对内存和性能有一些影响。然而,在大多数情况下,这种影响可以忽略不计。在您的情况下肯定可以忽略不计,因为您的样式适用于整个窗口并且仅在创建该窗口时使用。另外,您可以使用答案中显示的技术来减少影响,而不是每次都重新创建样式,而只创建 WindowChrome 的新实例。
  • 在 WindowChrome 上设置 x:Shared="False" 是不够的。窗口样式还必须包含 x:Shared="False"。
  • 如果您使用DynamicResource 来引用它(如我的示例中) - 就足够了。
猜你喜欢
  • 2014-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-18
  • 2014-12-23
相关资源
最近更新 更多