You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem description:
I'm using WritableBitmap in OnRender function inside a Control to draw something on the window, to access the Bitmap memory, I'm using WritableBitmap.TryLock but in some cases, it throws COMException.
Actual behavior:
System.Runtime.InteropServices.COMException
HResult=0x88982F0D
Message=There is already an outstanding read or write lock. (0x88982F0D)
Source=PresentationCore
StackTrace:
at System.Windows.Media.Imaging.WriteableBitmap.TryLock(Duration timeout)
at XXXXXX.OnRender(DrawingContext drawingContext) line 117
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
at System.Windows.Media.MediaContext.InvokeOnRenderCallback.DoWork()
at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
at System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(Object resizedCompositionTarget)
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)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
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)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run()
at XXXXXX.App.Main()
This exception was originally thrown at this call stack:
System.Windows.Media.Imaging.WriteableBitmap.TryLock(System.Windows.Duration)
XXXXXX.OnRender(System.Windows.Media.DrawingContext) in Spectrum.cs
System.Windows.UIElement.Arrange(System.Windows.Rect)
System.Windows.ContextLayoutManager.UpdateLayout()
System.Windows.ContextLayoutManager.UpdateLayoutCallback(object)
System.Windows.Media.MediaContext.InvokeOnRenderCallback.DoWork()
System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
System.Windows.Media.MediaContext.RenderMessageHandlerCore(object)
System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(object)
System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, object, int)
...
[Call Stack Truncated]
Expected behavior:
It should wait for a buffer until the timeout instead of throwing a COMException.
Minimal repro:
Not available, you can try lock & unlock the bitmap frequently, but since it's a racing condtion, it's really hard to repro directly and really depends on luck.
I did some analysis on the source code and I think it may because of the WritableBitmap implementation.
First of all, in the WritableBitmap.TryLock it checks if _lockCount == 0 and then process to AcquireBackBuffer.
In the AcquireBackBuffer function _pBackBuffer is check to see if there is an available buffer and if there is, it skip the acquisition of the buffer and return directly.
If AcquireBackBuffer succeed, the TryLock will put a lock not on _pBackBuffer, but on WicSourceHandle which was set to the same value as _pBackBuffer by AcquireBackBuffer when the first time buffer is acquired after previous commit.
And finally UnsubscribeFromCommittingBatch is called to prevent the commit of a locked bitmap.
Now, if we look at the Unlock function we can see the when the lock count go to zero in Unlock function the commit event is subscribed again with SubscribeToCommittingBatch().
And the problem here is the racing for the buffer. I'm not sure which thread will be able to call OnCommittingBatch but I guess it's not only Main Thread, otherwise a block waiting for the lock on main thread will never success if the commit also runs on main thread. This might be wrong but even in that case, I assume here might still have racing problem. But let's be simple and think they are two threads.
In the main thread, I called InvalidateVisual() and it ends in someone called the OnRender function in my class, and I called TryLock for the first time after last commit.
TryLock returns without issue, and I can draw something, Unlock, and return without any exception.
The CommittingBatchHandler is called in any other thread (I cannot comfirm if this can happen, when I use dnspy to break on this function, it never hits. ) , but before it reaches _pBackBuffer = null;, that is line 1386, I call trigger another InvalidateVisual() and eventually called TryLock again in the Main Thread.
In that thread, as _lockCount is now zero, AcquireBackBuffer is called again for the buffer, but noticing that _pBackBuffer is not null until line 1386 executed, this function returns true directly, without doing any event waiting or acquire another buffer.
After AcquireBackBuffer checks for the _pBackBuffer, the CommittingBatchHandler can continue execution and _pBackBuffer is set to null.
The _pBackBuffer is never used in TryLock and instead, WicSourceHandle is used as the handle to the Bitmap, TryLock can still lock the Bitmap using correct pointer rather than null even when the CommittingBatchHandler across line 1386.
Next, a MILCMD_DOUBLEBUFFEREDBITMAP_COPYFORWARD is sent to some thread (not sure which) and inside the doublebufferedbitmap.cpp this back buffer will be write locked.
And in the main thraed, the same buffer will be write locked and obvious here we have a racing.
In my case, I never seen exception on OnCommittingBatch though.
It might be wrong as I'm not sure how .net schedule the UI/Rendering threads, but there is such exception so I assume it's at least locked on two threads, and it's not me because I only lock on main thread.
The text was updated successfully, but these errors were encountered:
Problem description:
I'm using WritableBitmap in OnRender function inside a Control to draw something on the window, to access the Bitmap memory, I'm using WritableBitmap.TryLock but in some cases, it throws COMException.
Actual behavior:
Expected behavior:
It should wait for a buffer until the timeout instead of throwing a COMException.
Minimal repro:
Not available, you can try lock & unlock the bitmap frequently, but since it's a racing condtion, it's really hard to repro directly and really depends on luck.
I did some analysis on the source code and I think it may because of the WritableBitmap implementation.
First of all, in the WritableBitmap.TryLock it checks if
_lockCount == 0
and then process toAcquireBackBuffer
.In the
AcquireBackBuffer
function _pBackBuffer is check to see if there is an available buffer and if there is, it skip the acquisition of the buffer and return directly.If
AcquireBackBuffer
succeed, the TryLock will put a lock not on_pBackBuffer
, but on WicSourceHandle which was set to the same value as_pBackBuffer
byAcquireBackBuffer
when the first time buffer is acquired after previous commit.And finally
UnsubscribeFromCommittingBatch
is called to prevent the commit of a locked bitmap.Now, if we look at the
Unlock
function we can see the when the lock count go to zero in Unlock function the commit event is subscribed again withSubscribeToCommittingBatch()
.And the problem here is the racing for the buffer. I'm not sure which thread will be able to call
OnCommittingBatch
but I guess it's not only Main Thread, otherwise a block waiting for the lock on main thread will never success if the commit also runs on main thread. This might be wrong but even in that case, I assume here might still have racing problem. But let's be simple and think they are two threads.In the main thread, I called
InvalidateVisual()
and it ends in someone called theOnRender
function in my class, and I calledTryLock
for the first time after last commit.TryLock
returns without issue, and I can draw something,Unlock
, and return without any exception.The
CommittingBatchHandler
is called in any other thread (I cannot comfirm if this can happen, when I use dnspy to break on this function, it never hits. ) , but before it reaches_pBackBuffer = null;
, that is line 1386, I call trigger anotherInvalidateVisual()
and eventually calledTryLock
again in the Main Thread.In that thread, as
_lockCount
is now zero,AcquireBackBuffer
is called again for the buffer, but noticing that_pBackBuffer
is not null until line 1386 executed, this function returns true directly, without doing any event waiting or acquire another buffer.After
AcquireBackBuffer
checks for the_pBackBuffer
, theCommittingBatchHandler
can continue execution and_pBackBuffer
is set to null.The
_pBackBuffer
is never used inTryLock
and instead,WicSourceHandle
is used as the handle to the Bitmap,TryLock
can still lock the Bitmap using correct pointer rather thannull
even when theCommittingBatchHandler
across line 1386.Next, a
MILCMD_DOUBLEBUFFEREDBITMAP_COPYFORWARD
is sent to some thread (not sure which) and inside the doublebufferedbitmap.cpp this back buffer will be write locked.And in the main thraed, the same buffer will be write locked and obvious here we have a racing.
In my case, I never seen exception on
OnCommittingBatch
though.It might be wrong as I'm not sure how .net schedule the UI/Rendering threads, but there is such exception so I assume it's at least locked on two threads, and it's not me because I only lock on main thread.
The text was updated successfully, but these errors were encountered: