浏览代码

Fix: The synchronization context method Send is now blocking (#1854)

* Fix: The sychronization context method send 

The send method of the synchronization context has to block the caller thread in case it is not the main thread.

* Fix: Send has to invoke the action instantly

Send has to invoke the action instantly instead of enqueuing in case of main thread call.

* Added unit tests for synchronization post/send methods.

* Code formating

* Added unit test for MainLoopSyncContext.CreateCopy

Co-authored-by: saskathex <>
Co-authored-by: Tig Kindel <[email protected]>
saskathex 3 年之前
父节点
当前提交
dc99fb354f
共有 2 个文件被更改,包括 87 次插入2 次删除
  1. 14 2
      Terminal.Gui/Core/Application.cs
  2. 73 0
      UnitTests/ApplicationTests.cs

+ 14 - 2
Terminal.Gui/Core/Application.cs

@@ -279,9 +279,18 @@ namespace Terminal.Gui {
 
 
 			public override void Send (SendOrPostCallback d, object state)
 			public override void Send (SendOrPostCallback d, object state)
 			{
 			{
-				mainLoop.Invoke (() => {
+				if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) {
 					d (state);
 					d (state);
-				});
+				} else {
+					var wasExecuted = false;
+					mainLoop.Invoke (() => {
+						d (state);
+						wasExecuted = true;
+					});
+					while (!wasExecuted) {
+						Thread.Sleep (15);
+					}
+				}
 			}
 			}
 		}
 		}
 
 
@@ -307,6 +316,7 @@ namespace Terminal.Gui {
 		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
 		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
 
 
 		internal static bool _initialized = false;
 		internal static bool _initialized = false;
+		internal static int _mainThreadId = -1;
 
 
 		/// <summary>
 		/// <summary>
 		/// Initializes the Terminal.Gui application
 		/// Initializes the Terminal.Gui application
@@ -360,6 +370,7 @@ namespace Terminal.Gui {
 			Top = topLevelFactory ();
 			Top = topLevelFactory ();
 			Current = Top;
 			Current = Top;
 			supportedCultures = GetSupportedCultures ();
 			supportedCultures = GetSupportedCultures ();
+			_mainThreadId = Thread.CurrentThread.ManagedThreadId;
 			_initialized = true;
 			_initialized = true;
 		}
 		}
 
 
@@ -893,6 +904,7 @@ namespace Terminal.Gui {
 			RootMouseEvent = null;
 			RootMouseEvent = null;
 			RootKeyEvent = null;
 			RootKeyEvent = null;
 			Resized = null;
 			Resized = null;
+			_mainThreadId = -1;
 			NotifyNewRunState = null;
 			NotifyNewRunState = null;
 			NotifyStopRunState = null;
 			NotifyStopRunState = null;
 			_initialized = false;
 			_initialized = false;

+ 73 - 0
UnitTests/ApplicationTests.cs

@@ -84,6 +84,7 @@ namespace Terminal.Gui.Core {
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.MainLoop);
+			Assert.NotNull (SynchronizationContext.Current);
 		}
 		}
 
 
 		void Shutdown ()
 		void Shutdown ()
@@ -1335,5 +1336,77 @@ namespace Terminal.Gui.Core {
 				Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2);
 				Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2);
 			}
 			}
 		}
 		}
+
+		[Fact]
+		public void SynchronizationContext_Post ()
+		{
+			Init ();
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// non blocking
+				context.Post (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.False (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void SynchronizationContext_Send ()
+		{
+			Init ();
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// blocking
+				context.Send (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.True (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void SynchronizationContext_CreateCopy ()
+		{
+			Init ();
+
+			var context = SynchronizationContext.Current;
+			Assert.NotNull (context);
+
+			var contextCopy = context.CreateCopy ();
+			Assert.NotNull (contextCopy);
+
+			Assert.NotEqual (context, contextCopy);
+
+			Application.Shutdown ();
+		}
 	}
 	}
 }
 }