WPF中的图像处理不是线程安全的吗?

Ran*_*dom 6 c# wpf multithreading image

我正在一个标记为STA的不同线程中创建一个Window,这个窗口有一些控件和图像.

我去关闭这个窗口并打开主UI空间中的另一个窗口我有一个打印对话框并使用以下代码来获取FixedDocumentSequence:

var tempFileName = System.IO.Path.GetTempFileName();
File.Delete(tempFileName);

using (var xpsDocument = new XpsDocument(tempFileName, FileAccess.ReadWrite, CompressionOption.NotCompressed))
{
    var writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);

    writer.Write(this.DocumentPaginator);
}

using (var xpsDocument = new XpsDocument(tempFileName, FileAccess.Read, CompressionOption.NotCompressed))
{
    var xpsDoc = xpsDocument.GetFixedDocumentSequence();
    return xpsDoc;
}
Run Code Online (Sandbox Code Playgroud)

在线上:

writer.Write(this.DocumentPaginator);
Run Code Online (Sandbox Code Playgroud)

我从内部调用VerifyAccess获取InvalidOperationException,这是StackTrace:

bei System.Windows.Threading.Dispatcher.VerifyAccess()
bei System.Windows.Threading.DispatcherObject.VerifyAccess()
bei System.Windows.Media.Imaging.BitmapDecoder.get_IsDownloading()
bei System.Windows.Media.Imaging.BitmapFrameDecode.get_IsDownloading()
bei System.Windows.Media.Imaging.BitmapSource.FreezeCore(Boolean isChecking)
bei System.Windows.Freezable.Freeze(Boolean isChecking)
bei System.Windows.PropertyMetadata.DefaultFreezeValueCallback(DependencyObject d, DependencyProperty dp, EntryIndex entryIndex, PropertyMetadata metadata, Boolean isChecking)
bei System.Windows.Freezable.FreezeCore(Boolean isChecking)
bei System.Windows.Media.Animation.Animatable.FreezeCore(Boolean isChecking)
bei System.Windows.Freezable.Freeze()
bei System.Windows.Media.DrawingDrawingContext.DrawImage(ImageSource imageSource, Rect rectangle, AnimationClock rectangleAnimations)
bei System.Windows.Media.DrawingDrawingContext.DrawImage(ImageSource imageSource, Rect rectangle)
bei System.Windows.Media.DrawingContextDrawingContextWalker.DrawImage(ImageSource imageSource, Rect rectangle)
bei System.Windows.Media.RenderData.BaseValueDrawingContextWalk(DrawingContextWalker ctx)
bei System.Windows.Media.DrawingServices.DrawingGroupFromRenderData(RenderData renderData)
bei System.Windows.UIElement.GetDrawing()
bei System.Windows.Media.VisualTreeHelper.GetDrawing(Visual reference)
bei System.Windows.Xps.Serialization.VisualTreeFlattener.StartVisual(Visual visual)
bei System.Windows.Xps.Serialization.ReachVisualSerializer.SerializeTree(Visual visual, XmlWriter resWriter, XmlWriter bodyWriter)
bei System.Windows.Xps.Serialization.ReachVisualSerializer.SerializeObject(Object serializedObject)
bei System.Windows.Xps.Serialization.DocumentPageSerializer.SerializeChild(Visual child, SerializableObjectContext parentContext)
bei System.Windows.Xps.Serialization.DocumentPageSerializer.PersistObjectData(SerializableObjectContext serializableObjectContext)
bei System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(Object serializedObject)
bei System.Windows.Xps.Serialization.DocumentPageSerializer.SerializeObject(Object serializedObject)
bei System.Windows.Xps.Serialization.DocumentPaginatorSerializer.PersistObjectData(SerializableObjectContext serializableObjectContext)
bei System.Windows.Xps.Serialization.DocumentPaginatorSerializer.SerializeObject(Object serializedObject)
bei System.Windows.Xps.Serialization.XpsSerializationManager.SaveAsXaml(Object serializedObject)
bei System.Windows.Xps.XpsDocumentWriter.SaveAsXaml(Object serializedObject, Boolean isSync)
bei System.Windows.Xps.XpsDocumentWriter.Write(DocumentPaginator documentPaginator)
Run Code Online (Sandbox Code Playgroud)

由于StackTrace做了一些调用BitmapSource/BitmapDecoder,我想到尝试删除图像并将原位Image控件的Source设置为null

<Image Source={x:Null} />
Run Code Online (Sandbox Code Playgroud)

在我使用我的所有图像执行此操作后,我的代码运行顺利,并且没有更多异常被触发.

我尝试使用以下方法制作自定义图像以解决此问题:

public class CustomImage : Image
{
    public CustomImage()
    {
        this.Loaded += CustomImage_Loaded;
        this.SourceUpdated += CustomImage_SourceUpdated;
    }

    private void CustomImage_SourceUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
    {
        FreezeSource();
    }

    private void CustomImage_Loaded(object sender, System.Windows.RoutedEventArgs e)
    {
        FreezeSource();
    }

    private void FreezeSource()
    {
        if (this.Source == null)
            return;

        var freeze = this.Source as Freezable;
        if (freeze != null && freeze.CanFreeze && !freeze.IsFrozen)
            freeze.Freeze();
    }
}
Run Code Online (Sandbox Code Playgroud)

但我仍然得到错误.我更喜欢寻找适用于我的WPF应用程序中所有图像的解决方案.

希望我清楚自己,因为用2个线程解释并且在某个时刻有一个随机的异常是很奇怪的.

编辑: 经过一些进一步的测试后,我现在能够向您展示一个可重现的应用程序,并且希望它更清楚.

你需要3个窗口,1个文件夹和1个图像.在我的例子中,它的MainWindow.xaml Window1.xaml Window2.xaml

图像是文件夹的名称,并且有一个名为"plus.png"的图像.

MainWindow.xaml:

<StackPanel Orientation="Vertical">
    <StackPanel.Resources>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Margin"
                    Value="0,0,0,5"></Setter>
        </Style>
    </StackPanel.Resources>
    <Button Content="Open Window 1" Click="OpenWindowInNewThread" />
    <Button Content="Open Window 2" Click="OpenWindowInSameThread" />
</StackPanel>
Run Code Online (Sandbox Code Playgroud)

MainWindow.xaml.cs:

private void OpenWindowInNewThread(object sender, RoutedEventArgs e)
{
    var th = new Thread(() =>
    {
        SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));

        var x = new Window1();
        x.Closed += (s, ec) => Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);

        x.Show();

        System.Windows.Threading.Dispatcher.Run();
    });
    th.SetApartmentState(ApartmentState.STA);
    th.IsBackground = true;
    th.Start();
}

private void OpenWindowInSameThread(object sender, RoutedEventArgs e)
{
    var x = new Window2();
    x.Show();
}
Run Code Online (Sandbox Code Playgroud)

Window1.xaml:

<StackPanel Orientation="Horizontal">
    <ToggleButton Template="{StaticResource PlusToggleButton}" />
</StackPanel>
Run Code Online (Sandbox Code Playgroud)

Window1.xaml.cs: 那里只有构造函数没有代码......

Window2.xaml:

<StackPanel Orientation="Horizontal">
    <ToggleButton Template="{StaticResource PlusToggleButton}" />
    <Button Content="Print Me" Click="Print"></Button>
</StackPanel>
Run Code Online (Sandbox Code Playgroud)

Window2.xaml.cs:

public void Print(object sender, RoutedEventArgs e)
{
    PrintDialog pd = new PrintDialog();
    pd.PrintVisual(this, "HelloWorld");
}
Run Code Online (Sandbox Code Playgroud)

App.xaml中:

<ControlTemplate x:Key="PlusToggleButton"
                    TargetType="{x:Type ToggleButton}">
    <Image Name="Image"
            Source="/WpfApplication1;component/Images/plus.png"
            Stretch="None" />
</ControlTemplate>
Run Code Online (Sandbox Code Playgroud)

重现步骤:

  1. 在MainWindow中,单击"打开窗口1"按钮.
  2. 将在第二个UI-Thread中弹出一个窗口,关闭此窗口.
  3. 单击"打开窗口2"按钮
  4. 将在主UI-Thread中弹出一个窗口
  5. 按下按钮说"打印我",应用程序应该崩溃

希望它现在更容易帮助我.

EDIT2:

添加了缺少的代码部分,抱歉这个错误.

可能有助于解决问题的另一个信息是,当您按相反的顺序单击按钮 - 第一个窗口2而不是窗口1 - 而不是去尝试打印没有异常会触发,所以我仍然相信它的一些图像缓存问题,当图像首次加载到主UI-Thread打印时,如果不是,它将失败.

Ert*_*maa 4

Window1您正在使用由on创建的 UI 对象Window2

本质上,部分内容ControlTemplate在线程之间共享,这是不应该发生的(即BitmapImage,正如我从您的调用堆栈中读到的那样)。

您可以明确告知不共享:

<ControlTemplate x:Key="PlusToggleButton"
                    TargetType="{x:Type ToggleButton}"
                    x:Shared="False">
Run Code Online (Sandbox Code Playgroud)