Description
Further to the issue #663 that I created before, I have found more instances in which deadlocks can occur in Win2D.
The basic problem is that Win2D is susceptible to lock inversion. This is because sometimes a Direct2D lock (by which I mean the internal Direct2D factory lock which is presumably locked by most Direct2D calls or the explicit lock that the factory offers) is taken inside a Win2D lock (by which I mean a lock defined by the Win2D code) and sometimes the other way around.
One example (taken from the previous issue) of where Win2D takes a Win2D lock inside a Direct2D lock is in the CanvasSwapChain.CreateDrawingSession method. There are several examples of taking a Direct2D lock (implicitly) inside a Win2D lock e.g. ResourceManager::GetOrCreate for a CanvasVirtualBitmap due to the GetLocalBounds call.
The new deadlock I discovered (I think this was the cause of my deadlock although it was hard to reproduce) occurs with ICanvasImage.GetBounds. Basically this locks on the device context pool lock before creating a device context (if necessary), which uses an implicit Direct2D lock I assume. If, at the same time, on another thread, I call using(var l = Device.Lock()){image.GetBounds();}
, then this has the potential to deadlock (if you are unlucky) due to the inversion of the locks. (The code I'm actually calling is more like using(var l = Device.Lock()){GetOrCreate();}
for a CanvasVirtualBitmap, and GetOrCreate effectively calls GetBounds - the reason for taking the explicit lock in the first place is the workaround from the previous issue).
So now I've added another workaround to the list of workarounds I'm using, which is to call Device.Lock() before GetBounds()...
The locking issues really need addressing. Firstly, a it needs to be decided which is the innermost lock - the Direct2D one or Win2D locks. Given that it is possible to externally call Device.Lock(), I guess the Win2D locks have to be the innermost ones. Therefore whenever a Win2D lock is entered, either no Direct2D lock (implicit or explicit) should be taken inside it, or, prior to entering the lock, an explicit Direct2D lock should be taken. This should be applied to the entire library.
I realise this is a quite a tricky area, and I am not an expert so might comments may not be that helpful, but it would be good if this problem was addressed.