Is image processing in WPF unsafe?

I am creating a Window in another thread that is labeled STA. There are several controls and images in this window.

I than go in and close this window and open another window in the main thread of the user interface, I have a print dialog and use the following code to get 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; } 

In line:

 writer.Write(this.DocumentPaginator); 

I get an InvalidOperationException from an internal call to VerifyAccess , this is a 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) 

Since StackTrace makes a call to BitmapSource/BitmapDecoder , I was thinking of trying to remove the Images and set the in-place control source to null

 <Image Source={x:Null} /> 

After I did this with all my images, my code worked smoothly and there were no more exceptions.

I tried to create an individual solution to solve this problem with the following:

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

But I still get the error. Preferably, I am looking for a solution that works with all images in my WPF application.

I hope I made my clear, as it is rather strange to explain using two threads and a random exception at some point.

Edit: After some additional testing, I can now present you with a reproducible application with this problem, hope it becomes clearer with this.

You need 3 windows, 1 folder and 1 image. In my case its MainWindow.xaml Window1.xaml Window2.xaml

Images is the name of the folder, and there is an image in it with the name "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> 

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

Window1.xaml:

 <StackPanel Orientation="Horizontal"> <ToggleButton Template="{StaticResource PlusToggleButton}" /> </StackPanel> 

Window1.xaml.cs: There is no code there just a constructor ...

Window2.xaml:

 <StackPanel Orientation="Horizontal"> <ToggleButton Template="{StaticResource PlusToggleButton}" /> <Button Content="Print Me" Click="Print"></Button> </StackPanel> 

Window2.xaml.cs:

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

App.xaml:

 <ControlTemplate x:Key="PlusToggleButton" TargetType="{x:Type ToggleButton}"> <Image Name="Image" Source="/WpfApplication1;component/Images/plus.png" Stretch="None" /> </ControlTemplate> 

Steps to play:

  • In MainWindow, click the "Open Window 1" button.
  • A window will appear in the second user interface, close this window.
  • Click the "Open Window 2" button
  • A window will appear in the main user interface.
  • Press the "Print Me" button, the application should crash

Hopefully now it’s easier to help me.

Edit2:

Missing piece of code added, sorry for this error.

Other information that can help solve the problem is that when you click the buttons in the reverse order - first window 2 and window 1 - and than trying to print, the exception will fire, so I still think that it caches some image problem when the image is first loaded into the main print based on UI-Thread, if it does not complete.

+5
source share
3 answers

You are using the user interface object that Window1 created on Window2 .

Essentially, the parts of the ControlTemplate are split between threads, which shouldn't be (namely, BitmapImage , as I read from your freeze frame).

You can explicitly say that no exchange:

 <ControlTemplate x:Key="PlusToggleButton" TargetType="{x:Type ToggleButton}" x:Shared="False"> 
+3
source

An exception indicates that you are trying to access a control (essentially a class derived from DispatcherObject ) outside the stream that created it!

It’s hard to suggest fixing code based on your explanation of the threads. But a simple rule would be to make sure that you create agnostic control of the user interface thread in the user interface thread, and also do the same when accessing the properties of such a control.

looking at the code

 this.DocumentPaginator 

This property accessory seems to violate access to the stream (this means that the property is accessed by the stream that did not create it).

You can use the following code to run property accessories in the user interface stream (and you also need to make sure that such an object is created in the user interface stream)

 Application.Current.Dispatcher.Invoke( new Action(() => { //Your code/method name here } )); 

If the concept is new to you, you should read this MSDN page.

Here is the MSDN link for VerifyAccess

+3
source

With the current changes, this problem, of course, is associated with the "Print", "Click Handler", "Print" button. I tried this code in a new project and I cannot make it crash, so the error will most likely be in the Print function.

-2
source

Source: https://habr.com/ru/post/1211284/


All Articles