Browse Source

Merge pull request #5149 from AvaloniaUI/precise-nocom-locks

Only use non-pumping synchronization context when handling WM_PAINT and taking the lock in WM_SIZE
Nikita Tsukanov 5 years ago
parent
commit
a5c2126853

+ 2 - 2
src/Avalonia.Base/ApiCompatBaseline.txt

@@ -1,4 +1,4 @@
 Compat issues with assembly Avalonia.Base:
-CannotAddAbstractMembers : Member 'protected System.IObservable<Avalonia.AvaloniaPropertyChangedEventArgs> Avalonia.AvaloniaProperty.GetChanged()' is abstract in the implementation but is missing in the contract.
-TypesMustExist : Type 'Avalonia.Logging.DebugLogSink' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'public void Avalonia.Threading.AvaloniaSynchronizationContext..ctor(Avalonia.Threading.AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider)' does not exist in the implementation but it does exist in the contract.
+TypesMustExist : Type 'Avalonia.Threading.AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider' does not exist in the implementation but it does exist in the contract.
 Total Issues: 2

+ 2 - 23
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@@ -9,20 +9,6 @@ namespace Avalonia.Threading
     /// </summary>
     public class AvaloniaSynchronizationContext : SynchronizationContext
     {
-        public interface INonPumpingPlatformWaitProvider
-        {
-            int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
-        }
-
-        private readonly INonPumpingPlatformWaitProvider _waitProvider;
-
-        public AvaloniaSynchronizationContext(INonPumpingPlatformWaitProvider waitProvider)
-        {
-            _waitProvider = waitProvider;
-            if (_waitProvider != null)
-                SetWaitNotificationRequired();
-        }
-
         /// <summary>
         /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer.
         /// </summary>
@@ -38,8 +24,7 @@ namespace Avalonia.Threading
                 return;
             }
 
-            SetSynchronizationContext(new AvaloniaSynchronizationContext(AvaloniaLocator.Current
-                .GetService<INonPumpingPlatformWaitProvider>()));
+            SetSynchronizationContext(new AvaloniaSynchronizationContext());
         }
 
         /// <inheritdoc/>
@@ -57,12 +42,6 @@ namespace Avalonia.Threading
                 Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
         }
 
-        [PrePrepareMethod]
-        public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
-        {
-            if (_waitProvider != null)
-                return _waitProvider.Wait(waitHandles, waitAll, millisecondsTimeout);
-            return base.Wait(waitHandles, waitAll, millisecondsTimeout);
-        }
+
     }
 }

+ 14 - 0
src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace Avalonia.Utilities
+{
+    public class NonPumpingLockHelper
+    {
+        public interface IHelperImpl
+        {
+            IDisposable Use();
+        }
+
+        public static IDisposable Use() => AvaloniaLocator.Current.GetService<IHelperImpl>()?.Use();
+    }
+}

+ 1 - 1
src/Avalonia.FreeDesktop/DBusHelper.cs

@@ -43,7 +43,7 @@ namespace Avalonia.FreeDesktop
             public void Initialized()
             {
                 lock (_lock)
-                    _ctx = new AvaloniaSynchronizationContext(null);
+                    _ctx = new AvaloniaSynchronizationContext();
             }
         }
         public static Connection Connection { get; private set; }

+ 1 - 0
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -607,6 +607,7 @@ namespace Avalonia.Rendering
         private bool? UpdateScene()
         {
             Dispatcher.UIThread.VerifyAccess();
+            using var noPump = NonPumpingLockHelper.Use();
             lock (_sceneLock)
             {
                 if (_disposed)

+ 52 - 0
src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Threading;
+using Avalonia.Threading;
+using Avalonia.Utilities;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+    internal class NonPumpingSyncContext : SynchronizationContext, IDisposable
+    {
+        private readonly SynchronizationContext _inner;
+
+        private NonPumpingSyncContext(SynchronizationContext inner)
+        {
+            _inner = inner;
+            SetWaitNotificationRequired();
+            SetSynchronizationContext(this);
+        }
+
+        public override void Post(SendOrPostCallback d, object state) => _inner.Post(d, state);
+        public override void Send(SendOrPostCallback d, object state) => _inner.Send(d, state);
+        
+        [PrePrepareMethod]
+        public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
+        {
+            return UnmanagedMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll,
+                millisecondsTimeout, false);
+        }
+
+        public void Dispose() => SetSynchronizationContext(_inner);
+
+        public static IDisposable Use()
+        {
+            var current = Current;
+            if (current == null)
+            {
+                if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
+                    return null;
+            }
+            if (current is NonPumpingSyncContext)
+                return null;
+            
+            return new NonPumpingSyncContext(current);
+        }
+
+        internal class HelperImpl : NonPumpingLockHelper.IHelperImpl
+        {
+            IDisposable NonPumpingLockHelper.IHelperImpl.Use() => NonPumpingSyncContext.Use();
+        }
+    }
+}

+ 0 - 15
src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs

@@ -1,15 +0,0 @@
-using System;
-using Avalonia.Threading;
-using Avalonia.Win32.Interop;
-
-namespace Avalonia.Win32
-{
-    internal class NonPumpingWaitProvider : AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider
-    {
-        public int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
-        {
-            return UnmanagedMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll,
-                millisecondsTimeout, false);
-        }
-    }
-}

+ 2 - 1
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -16,6 +16,7 @@ using Avalonia.OpenGL.Egl;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Threading;
+using Avalonia.Utilities;
 using Avalonia.Win32.Input;
 using Avalonia.Win32.Interop;
 using static Avalonia.Win32.Interop.UnmanagedMethods;
@@ -110,7 +111,7 @@ namespace Avalonia.Win32
                 .Bind<IWindowingPlatform>().ToConstant(s_instance)
                 .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance)
-                .Bind<AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider>().ToConstant(new NonPumpingWaitProvider())
+                .Bind<NonPumpingLockHelper.IHelperImpl>().ToConstant(new NonPumpingSyncContext.HelperImpl())
                 .Bind<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider());
 
             Win32GlManager.Initialize();

+ 2 - 0
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@@ -348,6 +348,7 @@ namespace Avalonia.Win32
 
                 case WindowsMessage.WM_PAINT:
                 {
+                    using(NonPumpingSyncContext.Use())
                     using (_rendererLock.Lock())
                     {
                         if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
@@ -365,6 +366,7 @@ namespace Avalonia.Win32
 
                 case WindowsMessage.WM_SIZE:
                     {
+                        using(NonPumpingSyncContext.Use())
                         using (_rendererLock.Lock())
                         {
                             // Do nothing here, just block until the pending frame render is completed on the render thread