dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.04k stars 1.16k forks source link

InkCanvas.DynamicRenderer set different custom DynamicRenderer multiple times, throw an exception #7537

Closed jaelys closed 11 months ago

jaelys commented 1 year ago

Description

When using the touch screen, errors will occur, but not when using the mouse

On the touch screen, the two buttons switch and click multiple times to set a custom Dynamic Renderer for InkCanvas.DynamicRenderer. The crash always occurs after DrawPen OnRemoved

Button(DrawPen)、Button(DrawArrow)

`var dr = name switch {
"GeneralPen" => new DrawPen(this),
"ArrowPen" => new DrawArrow(this),
_ => new DrawPen(this), };

DynamicRenderer = dr; `

If set different custom DynamicRenderer multiple times, throw an exception

App_DispatcherUnhandledException--Object reference not set to an instance of an object. at System.Windows.Input.StylusPlugIns.DynamicRenderer.TransitionComplete(StrokeInfo si) at System.Windows.Input.StylusPlugIns.DynamicRenderer.<>c__DisplayClass19_0.<NotifyAppOfDRThreadRenderComplete>b__0(Object unused) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)

PC-Test-develop-2.zip

Reproduction Steps

Ditto description

Expected behavior

No exception will be thrown

Actual behavior

Throw thread exception

Regression?

No response

Known Workarounds

No response

Impact

No response

Configuration

.NET Core Version: .NET 7.0.103 SDK Windows version: Version 22H2 (OS Build 19045.2546)

Other information

No response

miloush commented 1 year ago

Please provide a repro project having the issue.

lindexi commented 1 year ago

@jaelys Sorry, I run your demo but I do not repro it.

jaelys commented 1 year ago

@lindexi Thank you for your help. I updated the Demo and annotated some code that may prevent exceptions. These codes are valid, but not 100%. Now we should be able to simply reproduce the exception. Just press the Pen Button several times, switch another button, and repeat it several times

感謝林德熙大神

lindexi commented 1 year ago

@jaelys Sorry, I run the PC-Test-develop-2 , but I do not repro it.

InkCanvas DynamicRenderer set different custom DynamicRenderer multiple times

lindexi commented 1 year ago

@jaelys Thank you. And I know why. This is the thread-safe issues in WPF.

The DynamicRenderer call the OnRemoved before the TransitionComplete. And the OnRemoved will set the _applicationDispatcher to null but the TransitionComplete will use the _applicationDispatcher which will throw the NullReferenceException.

I add the break point output in the OnRemoved and NotifyAppOfDRThreadRenderComplete and TransitionComplete mehtod. And then I find the output is:

14:12:08:043    NotifyAppOfDRThreadRenderComplete
14:12:08:043    OnRemoved
14:12:08:043    TransitionComplete

It means this is a thread-safe issue.


The NotifyAppOfDRThreadRenderComplete call stack:

>   PresentationCore.dll!System.Windows.Input.StylusPlugIns.DynamicRenderer.NotifyAppOfDRThreadRenderComplete(System.Windows.Input.StylusPlugIns.DynamicRenderer.StrokeInfo si)     
    PresentationCore.dll!System.Windows.Input.StylusPlugIns.DynamicRenderer.OnDRThreadRenderComplete(object sender, System.EventArgs e)     
    PresentationCore.dll!System.Windows.Media.MediaContext.CommitChannel()  
    PresentationCore.dll!System.Windows.Media.MediaContext.Render(System.Windows.Media.ICompositionTarget resizedCompositionTarget)     
    PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandlerCore(object resizedCompositionTarget)    
    PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandler(object resizedCompositionTarget)    
    WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs)  
    WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) 
    WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl()   
    WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state)  
    WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(object obj)   
    System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)   
    System.Private.CoreLib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)   
    WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext executionContext, System.Threading.ContextCallback callback, object state)  
    WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke()   
    WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue()  
    WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)  
    WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) 
    WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) 
    WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs)  
    WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) 
    WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs)   
    WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam)  
    WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame)   
    WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame)   
    WindowsBase.dll!System.Windows.Threading.Dispatcher.Run()   
    PresentationCore.dll!System.Windows.Input.StylusPlugIns.DynamicRendererThreadManager.DynamicRendererThreadManagerWorker.InkingThreadProc()  
    System.Private.CoreLib.dll!System.Threading.Thread.StartHelper.Callback(object state)   
    System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)   
    System.Private.CoreLib.dll!System.Threading.Thread.StartCallback()  

This thread is DynamicRenderer thread.


The OnRemoved call stack:

>   PresentationCore.dll!System.Windows.Input.StylusPlugIns.DynamicRenderer.OnRemoved()     
    InkTest.dll!InkTest.DrawPen.OnRemoved()     
    PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugIn.Removed()  
    PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugInCollection.RemoveItem.AnonymousMethod__1()  
    PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugInCollection.ExecuteWithPotentialLock(System.Action action)   
    PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugInCollection.RemoveItem.AnonymousMethod__0()  
    PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugInCollection.ExecuteWithPotentialDispatcherDisable(System.Action action)  
    PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugInCollection.RemoveItem(int index)    
    System.Private.CoreLib.dll!System.Collections.ObjectModel.Collection<System.Windows.Input.StylusPlugIns.StylusPlugIn>.RemoveAt(int index)   
    PresentationFramework.dll!System.Windows.Controls.InkCanvas.DynamicRenderer.set(System.Windows.Input.StylusPlugIns.DynamicRenderer value)   
    InkTest.dll!InkTest.InkCanvasEx.SetInkBrush(string name)    
    InkTest.dll!InkTest.PenButton.Button_Click(object sender, System.Windows.RoutedEventArgs e) 

This thread is main UI thread.


Why can’t it be reproduced in mouse? Because it is a thread-safe issue, and the touch will enter the logic code which runs in Stylus Input thread, but the mouse will run in main UI thread.

jaelys commented 1 year ago

@jaelys Thank you. And I know why. This is the thread-safe issues in WPF.

The DynamicRenderer call the OnRemoved before the TransitionComplete. And the OnRemoved will set the _applicationDispatcher to null but the TransitionComplete will use the _applicationDispatcher which will throw the NullReferenceException.

I use the physical touch whiteboard, which is easy to reproduce, Abnormality cannot be reproduced in mouse or analog touch

Thank you again for your help

lindexi commented 1 year ago

@jaelys Thanks for your feedback, and I am trying to fix this issues.