| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 | 
							- using System;
 
- using System.Collections.Generic;
 
- using System.Diagnostics;
 
- using System.Diagnostics.CodeAnalysis;
 
- using System.Linq;
 
- using System.Threading;
 
- using System.Threading.Tasks;
 
- using Avalonia.Controls.Platform;
 
- using Avalonia.Threading;
 
- using Avalonia.Utilities;
 
- using Xunit;
 
- namespace Avalonia.Base.UnitTests;
 
- public partial class DispatcherTests
 
- {
 
-     class SimpleDispatcherImpl : IDispatcherImpl, IDispatcherImplWithPendingInput
 
-     {
 
-         private readonly Thread _loopThread = Thread.CurrentThread;
 
-         private readonly object _lock = new();
 
-         public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _loopThread;
 
-         public void Signal()
 
-         {
 
-             lock (_lock)
 
-                 AskedForSignal = true;
 
-         }
 
-         public event Action Signaled;
 
-         public event Action Timer;
 
-         public long? NextTimer { get; private set; }
 
-         public bool AskedForSignal { get; private set; }
 
-         
 
-         public void UpdateTimer(long? dueTimeInTicks)
 
-         {
 
-             NextTimer = dueTimeInTicks;
 
-         }
 
-         public long Now { get; set; }
 
-         public void ExecuteSignal()
 
-         {
 
-             lock (_lock)
 
-             {
 
-                 if (!AskedForSignal)
 
-                     return;
 
-                 AskedForSignal = false;
 
-             }
 
-             Signaled?.Invoke();
 
-         }
 
-         public void ExecuteTimer()
 
-         {
 
-             if (NextTimer == null)
 
-                 return;
 
-             Now = NextTimer.Value;
 
-             Timer?.Invoke();
 
-         }
 
-         public bool CanQueryPendingInput => TestInputPending != null;
 
-         public bool HasPendingInput => TestInputPending == true;
 
-         public bool? TestInputPending { get; set; }
 
-     }
 
-     class SimpleDispatcherWithBackgroundProcessingImpl : SimpleDispatcherImpl, IDispatcherImplWithExplicitBackgroundProcessing
 
-     {
 
-         public bool AskedForBackgroundProcessing { get; private set; }
 
-         public event Action ReadyForBackgroundProcessing;
 
-         public void RequestBackgroundProcessing()
 
-         {
 
-             if (!CurrentThreadIsLoopThread)
 
-                 throw new InvalidOperationException();
 
-             AskedForBackgroundProcessing = true;
 
-         }
 
-         public void FireBackgroundProcessing()
 
-         {
 
-             if(!AskedForBackgroundProcessing)
 
-                 return;
 
-             AskedForBackgroundProcessing = false;
 
-             ReadyForBackgroundProcessing?.Invoke();
 
-         }
 
-     }
 
-     
 
-     class SimpleControlledDispatcherImpl : SimpleDispatcherWithBackgroundProcessingImpl, IControlledDispatcherImpl
 
-     {
 
-         private readonly bool _useTestTimeout = true;
 
-         private readonly CancellationToken? _cancel;
 
-         public int RunLoopCount { get; private set; }
 
-         
 
-         public SimpleControlledDispatcherImpl()
 
-         {
 
-             
 
-         }
 
-         public SimpleControlledDispatcherImpl(CancellationToken cancel, bool useTestTimeout = false)
 
-         {
 
-             _useTestTimeout = useTestTimeout;
 
-             _cancel = cancel;
 
-         }
 
-         
 
-         public void RunLoop(CancellationToken token)
 
-         {
 
-             RunLoopCount++;
 
-             var st = Stopwatch.StartNew();
 
-             while (!token.IsCancellationRequested || _cancel?.IsCancellationRequested == true)
 
-             {
 
-                 FireBackgroundProcessing();
 
-                 ExecuteSignal();
 
-                 if (_useTestTimeout)
 
-                     Assert.True(st.ElapsedMilliseconds < 4000, "RunLoop exceeded test time quota");
 
-                 else
 
-                     Thread.Sleep(10);
 
-             }
 
-         }
 
-     }
 
-     
 
-     
 
-     [Fact]
 
-     public void DispatcherExecutesJobsAccordingToPriority()
 
-     {
 
-         var impl = new SimpleDispatcherImpl();
 
-         var disp = new Dispatcher(impl);
 
-         var actions = new List<string>();
 
-         disp.Post(()=>actions.Add("Background"), DispatcherPriority.Background);
 
-         disp.Post(()=>actions.Add("Render"), DispatcherPriority.Render);
 
-         disp.Post(()=>actions.Add("Input"), DispatcherPriority.Input);
 
-         Assert.True(impl.AskedForSignal);
 
-         impl.ExecuteSignal();
 
-         Assert.Equal(new[] { "Render", "Input", "Background" }, actions);
 
-     }
 
-     
 
-     [Fact]
 
-     public void DispatcherPreservesOrderWhenChangingPriority()
 
-     {
 
-         var impl = new SimpleDispatcherImpl();
 
-         var disp = new Dispatcher(impl);
 
-         var actions = new List<string>();
 
-         var toPromote = disp.InvokeAsync(()=>actions.Add("PromotedRender"), DispatcherPriority.Background);
 
-         var toPromote2 = disp.InvokeAsync(()=>actions.Add("PromotedRender2"), DispatcherPriority.Input);
 
-         disp.Post(() => actions.Add("Render"), DispatcherPriority.Render);
 
-         
 
-         toPromote.Priority = DispatcherPriority.Render;
 
-         toPromote2.Priority = DispatcherPriority.Render;
 
-         
 
-         Assert.True(impl.AskedForSignal);
 
-         impl.ExecuteSignal();
 
-         
 
-         Assert.Equal(new[] { "PromotedRender", "PromotedRender2", "Render" }, actions);
 
-     }
 
-     [Fact]
 
-     public void DispatcherStopsItemProcessingWhenInteractivityDeadlineIsReached()
 
-     {
 
-         var impl = new SimpleDispatcherImpl();
 
-         var disp = new Dispatcher(impl);
 
-         var actions = new List<int>();
 
-         for (var c = 0; c < 10; c++)
 
-         {
 
-             var itemId = c;
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add(itemId);
 
-                 impl.Now += 20;
 
-             }, DispatcherPriority.Background);
 
-         }
 
-         Assert.False(impl.AskedForSignal);
 
-         Assert.NotNull(impl.NextTimer);
 
-         for (var c = 0; c < 4; c++)
 
-         {
 
-             Assert.NotNull(impl.NextTimer);
 
-             Assert.False(impl.AskedForSignal);
 
-             impl.ExecuteTimer();
 
-             Assert.False(impl.AskedForSignal);
 
-             impl.ExecuteSignal();
 
-             var expectedCount = (c + 1) * 3;
 
-             if (c == 3)
 
-                 expectedCount = 10;
 
-             
 
-             Assert.Equal(Enumerable.Range(0, expectedCount), actions);
 
-             Assert.False(impl.AskedForSignal);
 
-             if (c < 3)
 
-             {
 
-                 Assert.True(impl.NextTimer > impl.Now);
 
-             }
 
-             else
 
-                 Assert.Null(impl.NextTimer);
 
-         }
 
-     }
 
-     
 
-     
 
-     [Fact]
 
-     public void DispatcherStopsItemProcessingWhenInputIsPending()
 
-     {
 
-         var impl = new SimpleDispatcherImpl();
 
-         impl.TestInputPending = true;
 
-         var disp = new Dispatcher(impl);
 
-         var actions = new List<int>();
 
-         for (var c = 0; c < 10; c++)
 
-         {
 
-             var itemId = c;
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add(itemId);
 
-                 if (itemId == 0 || itemId == 3 || itemId == 7)
 
-                     impl.TestInputPending = true;
 
-             }, DispatcherPriority.Background);
 
-         }
 
-         Assert.False(impl.AskedForSignal);
 
-         Assert.NotNull(impl.NextTimer);
 
-         impl.TestInputPending = false;
 
-         for (var c = 0; c < 4; c++)
 
-         {
 
-             Assert.NotNull(impl.NextTimer);
 
-             impl.ExecuteTimer();
 
-             Assert.False(impl.AskedForSignal);
 
-             var expectedCount = c switch
 
-             {
 
-                 0 => 1,
 
-                 1 => 4,
 
-                 2 => 8,
 
-                 3 => 10,
 
-                 _ => throw new InvalidOperationException($"Unexpected value {c}")
 
-             };
 
-             
 
-             Assert.Equal(Enumerable.Range(0, expectedCount), actions);
 
-             Assert.False(impl.AskedForSignal);
 
-             if (c < 3)
 
-             {
 
-                 Assert.True(impl.NextTimer > impl.Now);
 
-                 impl.Now = impl.NextTimer.Value + 1;
 
-             }
 
-             else
 
-                 Assert.Null(impl.NextTimer);
 
-             impl.TestInputPending = false;
 
-         }
 
-     }
 
-     [Theory,
 
-      InlineData(false, false),
 
-      InlineData(false, true),
 
-      InlineData(true, false),
 
-      InlineData(true, true)]
 
-     public void CanWaitForDispatcherOperationFromTheSameThread(bool controlled, bool foreground)
 
-     {
 
-         var impl = controlled ? new SimpleControlledDispatcherImpl() : new SimpleDispatcherImpl();
 
-         var disp = new Dispatcher(impl);
 
-         bool finished = false;
 
-         disp.InvokeAsync(() => finished = true,
 
-             foreground ? DispatcherPriority.Default : DispatcherPriority.Background).Wait();
 
-         Assert.True(finished);
 
-         if (controlled) 
 
-             Assert.Equal(foreground ? 0 : 1, ((SimpleControlledDispatcherImpl)impl).RunLoopCount);
 
-     }
 
-     class DispatcherServices : IDisposable
 
-     {
 
-         private readonly IDisposable _scope;
 
-         public DispatcherServices(IDispatcherImpl impl)
 
-         {
 
-             _scope = AvaloniaLocator.EnterScope();
 
-             AvaloniaLocator.CurrentMutable.Bind<IDispatcherImpl>().ToConstant(impl);
 
-             Dispatcher.ResetForUnitTests();
 
-             SynchronizationContext.SetSynchronizationContext(null);
 
-         }
 
-         
 
-         public void Dispose()
 
-         {
 
-             Dispatcher.ResetForUnitTests();
 
-             _scope.Dispose();
 
-             SynchronizationContext.SetSynchronizationContext(null);
 
-         }
 
-     }
 
-     
 
-     [Fact]
 
-     public void ExitAllFramesShouldExitAllFramesAndBeAbleToContinue()
 
-     {
 
-         using (new DispatcherServices(new SimpleControlledDispatcherImpl()))
 
-         {
 
-             var actions = new List<string>();
 
-             var disp = Dispatcher.UIThread;
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add("Nested frame");
 
-                 Dispatcher.UIThread.MainLoop(CancellationToken.None);
 
-                 actions.Add("Nested frame exited");
 
-             });
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add("ExitAllFrames");
 
-                 disp.ExitAllFrames();
 
-             });
 
-             disp.MainLoop(CancellationToken.None);
 
-             
 
-             Assert.Equal(new[] { "Nested frame", "ExitAllFrames", "Nested frame exited" }, actions);
 
-             actions.Clear();
 
-             
 
-             var secondLoop = new CancellationTokenSource();
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add("Callback after exit");
 
-                 secondLoop.Cancel();
 
-             });
 
-             disp.MainLoop(secondLoop.Token);
 
-             Assert.Equal(new[] { "Callback after exit" }, actions);
 
-         }
 
-     }
 
-     
 
-         
 
-     [Fact]
 
-     public void ShutdownShouldExitAllFramesAndNotAllowNewFrames()
 
-     {
 
-         using (new DispatcherServices(new SimpleControlledDispatcherImpl()))
 
-         {
 
-             var actions = new List<string>();
 
-             var disp = Dispatcher.UIThread;
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add("Nested frame");
 
-                 Dispatcher.UIThread.MainLoop(CancellationToken.None);
 
-                 actions.Add("Nested frame exited");
 
-             });
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add("Shutdown");
 
-                 disp.BeginInvokeShutdown(DispatcherPriority.Normal);
 
-             });
 
-             
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add("Nested frame after shutdown");
 
-                 // This should exit immediately and not run any jobs
 
-                 Dispatcher.UIThread.MainLoop(CancellationToken.None);
 
-                 actions.Add("Nested frame after shutdown exited");
 
-             });
 
-             
 
-             var criticalFrameAfterShutdown = new DispatcherFrame(false);
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add("Critical frame after shutdown");
 
-                 
 
-                 Dispatcher.UIThread.PushFrame(criticalFrameAfterShutdown);
 
-                 actions.Add("Critical frame after shutdown exited");
 
-             });
 
-             disp.Post(() =>
 
-             {
 
-                 actions.Add("Stop critical frame");
 
-                 criticalFrameAfterShutdown.Continue = false;
 
-             });
 
-             disp.MainLoop(CancellationToken.None);
 
-             Assert.Equal(new[]
 
-             {
 
-                 "Nested frame", 
 
-                 "Shutdown",
 
-                 // Normal nested frames are supposed to exit immediately
 
-                 "Nested frame after shutdown", "Nested frame after shutdown exited",
 
-                 // if frame is configured to not answer dispatcher requests, it should be allowed to run
 
-                 "Critical frame after shutdown", "Stop critical frame", "Critical frame after shutdown exited",
 
-                 // After 3-rd level frames have exited, the normal nested frame exits too
 
-                 "Nested frame exited"
 
-             }, actions);
 
-             actions.Clear();
 
-             
 
-             disp.Post(()=>actions.Add("Frame after shutdown finished"));
 
-             Assert.Throws<InvalidOperationException>(() => disp.MainLoop(CancellationToken.None));
 
-             Assert.Empty(actions);
 
-         }
 
-     }
 
-     class WaitHelper : SynchronizationContext, NonPumpingLockHelper.IHelperImpl
 
-     {
 
-         public int WaitCount;
 
-         public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
 
-         {
 
-             WaitCount++;
 
-             return base.Wait(waitHandles, waitAll, millisecondsTimeout);
 
-         }
 
-     }
 
-     
 
-     [Fact]
 
-     public void DisableProcessingShouldStopProcessing()
 
-     {
 
-         using (new DispatcherServices(new SimpleControlledDispatcherImpl()))
 
-         {
 
-             var helper = new WaitHelper();
 
-             AvaloniaLocator.CurrentMutable.Bind<NonPumpingLockHelper.IHelperImpl>().ToConstant(helper);
 
-             using (Dispatcher.UIThread.DisableProcessing())
 
-             {
 
-                 Assert.True(SynchronizationContext.Current is NonPumpingSyncContext);
 
-                 Assert.Throws<InvalidOperationException>(() => Dispatcher.UIThread.MainLoop(CancellationToken.None));
 
-                 Assert.Throws<InvalidOperationException>(() => Dispatcher.UIThread.RunJobs());
 
-             }
 
-             var avaloniaContext = new AvaloniaSynchronizationContext(Dispatcher.UIThread, DispatcherPriority.Default, true);
 
-             SynchronizationContext.SetSynchronizationContext(avaloniaContext);
 
-             var waitHandle = new ManualResetEvent(true);
 
-             
 
-             helper.WaitCount = 0;
 
-             waitHandle.WaitOne(100);
 
-             Assert.Equal(0, helper.WaitCount);
 
-             using (Dispatcher.UIThread.DisableProcessing())
 
-             {
 
-                 Assert.Equal(avaloniaContext, SynchronizationContext.Current);
 
-                 waitHandle.WaitOne(100);
 
-                 Assert.Equal(1, helper.WaitCount);
 
-             }
 
-         }
 
-     }
 
-     [Fact]
 
-     public void DispatcherOperationsHaveContextWithProperPriority()
 
-     {
 
-         using (new DispatcherServices(new SimpleControlledDispatcherImpl()))
 
-         {
 
-             SynchronizationContext.SetSynchronizationContext(null);
 
-             var disp = Dispatcher.UIThread;
 
-             var priorities = new List<DispatcherPriority>();
 
-             void DumpCurrentPriority() =>
 
-                 priorities.Add(((AvaloniaSynchronizationContext)SynchronizationContext.Current!).Priority);
 
-                 
 
-                 
 
-             disp.Post(DumpCurrentPriority, DispatcherPriority.Normal);
 
-             disp.Post(DumpCurrentPriority, DispatcherPriority.Loaded);
 
-             disp.Post(DumpCurrentPriority, DispatcherPriority.Input);
 
-             disp.Post(() =>
 
-             {
 
-                 DumpCurrentPriority();
 
-                 disp.ExitAllFrames();
 
-             }, DispatcherPriority.Background);
 
-             disp.MainLoop(CancellationToken.None);
 
-             disp.Invoke(DumpCurrentPriority, DispatcherPriority.Send);
 
-             disp.Invoke(() =>
 
-             {
 
-                 DumpCurrentPriority();
 
-                 return 1;
 
-             }, DispatcherPriority.Send);
 
-             Assert.Equal(
 
-                 new[]
 
-                 {
 
-                     DispatcherPriority.Normal, DispatcherPriority.Loaded, DispatcherPriority.Input, DispatcherPriority.Background,
 
-                     DispatcherPriority.Send, DispatcherPriority.Send,
 
-                 },
 
-                 priorities);
 
-         }
 
-     }
 
-     [Fact]
 
-     [SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method", Justification = "Tests the dispatcher itself")]
 
-     public void DispatcherInvokeAsyncUnwrapsTasks()
 
-     {
 
-         int asyncMethodStage = 0;
 
-         
 
-         async Task AsyncMethod()
 
-         {
 
-             asyncMethodStage = 1;
 
-             await Task.Delay(200);
 
-             asyncMethodStage = 2;
 
-         }
 
-         
 
-         async Task<int> AsyncMethodWithResult()
 
-         {
 
-             await Task.Delay(100);
 
-             return 1;
 
-         }
 
-         
 
-         async Task Test()
 
-         {
 
-             await Dispatcher.UIThread.InvokeAsync(AsyncMethod);
 
-             Assert.Equal(2, asyncMethodStage);
 
-             Assert.Equal(1, await Dispatcher.UIThread.InvokeAsync(AsyncMethodWithResult));
 
-             asyncMethodStage = 0;
 
-             
 
-             await Dispatcher.UIThread.InvokeAsync(AsyncMethod, DispatcherPriority.Default);
 
-             Assert.Equal(2, asyncMethodStage);
 
-             Assert.Equal(1, await Dispatcher.UIThread.InvokeAsync(AsyncMethodWithResult, DispatcherPriority.Default));
 
-             
 
-             Dispatcher.UIThread.ExitAllFrames();
 
-         }
 
-         
 
-         using (new DispatcherServices(new ManagedDispatcherImpl(null)))
 
-         {
 
-             var t = Test();
 
-             var cts = new CancellationTokenSource();
 
-             Task.Delay(3000).ContinueWith(_ => cts.Cancel());
 
-             Dispatcher.UIThread.MainLoop(cts.Token);
 
-             Assert.True(t.IsCompletedSuccessfully);
 
-             t.GetAwaiter().GetResult();
 
-         }
 
-     }
 
- }
 
 
  |