diff --git a/src/Compatibility/ControlGallery/src/Issues.Shared/IsInvokeRequiredRaceCondition.cs b/src/Compatibility/ControlGallery/src/Issues.Shared/IsInvokeRequiredRaceCondition.cs index 8611d4a62d36..09fe14f4cbdd 100644 --- a/src/Compatibility/ControlGallery/src/Issues.Shared/IsInvokeRequiredRaceCondition.cs +++ b/src/Compatibility/ControlGallery/src/Issues.Shared/IsInvokeRequiredRaceCondition.cs @@ -58,7 +58,7 @@ List GenerateTasks() #if UITEST [Test] - [Microsoft.Maui.Controls.Compatibility.UITests.FailsOnMauiIOS] + [Microsoft.Maui.Controls.Compatibility.UITests.MovedToAppium] public void ShouldNotCrash() { RunningApp.Tap(q => q.Marked("crashButton")); diff --git a/src/Compatibility/ControlGallery/src/Issues.Shared/TestPages/TestPages.cs b/src/Compatibility/ControlGallery/src/Issues.Shared/TestPages/TestPages.cs index 29c131db9e72..b5f6ccb6278a 100644 --- a/src/Compatibility/ControlGallery/src/Issues.Shared/TestPages/TestPages.cs +++ b/src/Compatibility/ControlGallery/src/Issues.Shared/TestPages/TestPages.cs @@ -958,6 +958,9 @@ public object GetService(Type serviceType) if (serviceType == typeof(IDispatcher)) return _dispatcher; + if (serviceType == typeof(ApplicationDispatcher)) + return new ApplicationDispatcher(_dispatcher); + throw new NotImplementedException(); } diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/IsInvokeRequiredRaceCondition.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/IsInvokeRequiredRaceCondition.cs new file mode 100644 index 000000000000..eaa061bc0e49 --- /dev/null +++ b/src/Controls/samples/Controls.Sample.UITests/Issues/IsInvokeRequiredRaceCondition.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Graphics; + +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.None, 1, "Application.Current.Dispatcher.IsDispatchRequired race condition causes crash")] +public class IsInvokeRequiredRaceCondition : TestContentPage +{ + protected override void Init() + { + var button = new Button + { + AutomationId = "crashButton", + Text = "Start Test" + }; + + var success = new Label { Text = "Success", IsVisible = false, AutomationId = "successLabel" }; + + var instructions = new Label { Text = "Click the Start Test button. " }; + + Content = new StackLayout + { + HorizontalOptions = LayoutOptions.Fill, + VerticalOptions = LayoutOptions.Fill, + Children = { instructions, success, button } + }; + + button.Clicked += async (sender, args) => + { + await Task.WhenAll(GenerateTasks()); + success.IsVisible = true; + }; + } + + List GenerateTasks() + { + var result = new List(); + + for (int n = 0; n < 1000; n++) + { + result.Add(Task.Run(() => { var t = Application.Current.Dispatcher.IsDispatchRequired; })); + } + + return result; + } + +} \ No newline at end of file diff --git a/src/Controls/src/Core/DispatcherExtensions.cs b/src/Controls/src/Core/DispatcherExtensions.cs index 3d9251a9947a..92bfedc3a2f3 100644 --- a/src/Controls/src/Core/DispatcherExtensions.cs +++ b/src/Controls/src/Core/DispatcherExtensions.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui.Dispatching; +using Microsoft.Maui.Hosting; namespace Microsoft.Maui.Controls { @@ -22,11 +23,17 @@ bindableObject is Element element && if (Dispatcher.GetForCurrentThread() is IDispatcher globalDispatcher) return globalDispatcher; - // If BO is of type Application then check for its Dispatcher + // If BO is of type Application then return the Dispatcher from ApplicationDispatcher if (bindableObject is Application app && - app.FindMauiContext() is IMauiContext appMauiContext && - appMauiContext.Services.GetService() is IDispatcher appHandlerDispatcher) - return appHandlerDispatcher; + app.FindMauiContext() is IMauiContext appMauiContext) + { + if (appMauiContext.Services.GetOptionalApplicationDispatcher() is IDispatcher appDispatcherServiceDispatcher) + return appDispatcherServiceDispatcher; + + // If BO is of type Application then check for its Dispatcher + if (appMauiContext.Services.GetService() is IDispatcher appHandlerDispatcher) + return appHandlerDispatcher; + } // try looking on the static app // We don't include Application because Application.Dispatcher will call diff --git a/src/Controls/tests/UITests/Tests/Issues/IsInvokeRequiredRaceCondition.cs b/src/Controls/tests/UITests/Tests/Issues/IsInvokeRequiredRaceCondition.cs new file mode 100644 index 000000000000..7511e5832d61 --- /dev/null +++ b/src/Controls/tests/UITests/Tests/Issues/IsInvokeRequiredRaceCondition.cs @@ -0,0 +1,22 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.AppiumTests.Issues; + + public class IsInvokeRequiredRaceCondition : _IssuesUITest + { + public IsInvokeRequiredRaceCondition(TestDevice device) : base(device) + { + } + + public override string Issue => "Application.Current.Dispatcher.IsDispatchRequired race condition causes crash"; + + [Test] + public void ApplicationDispatcherIsInvokeRequiredRaceConditionCausesCrash() + { + App.WaitForElement("crashButton"); + App.Click("crashButton"); + App.WaitForElement("successLabel"); + } + } \ No newline at end of file diff --git a/src/Core/src/Hosting/Dispatching/AppHostBuilderExtensions.cs b/src/Core/src/Hosting/Dispatching/AppHostBuilderExtensions.cs index 516e7395769e..1f3f4154117d 100644 --- a/src/Core/src/Hosting/Dispatching/AppHostBuilderExtensions.cs +++ b/src/Core/src/Hosting/Dispatching/AppHostBuilderExtensions.cs @@ -65,7 +65,7 @@ static IDispatcher GetDispatcher(IServiceProvider services) services.CreateLogger()?.LogWarning("Replaced an existing DispatcherProvider with one from the service provider."); } - return Dispatcher.GetForCurrentThread()!; + return Dispatcher.GetForCurrentThread() ?? services.GetRequiredService().Dispatcher; } class ApplicationDispatcherInitializer : IMauiInitializeService @@ -84,4 +84,4 @@ public void Initialize(IServiceProvider services) } } } -} \ No newline at end of file +} diff --git a/src/Core/src/Properties/AssemblyInfo.cs b/src/Core/src/Properties/AssemblyInfo.cs index 3959e4ec9a67..c420557a9c12 100644 --- a/src/Core/src/Properties/AssemblyInfo.cs +++ b/src/Core/src/Properties/AssemblyInfo.cs @@ -10,6 +10,9 @@ [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility.Tizen")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility.ControlGallery")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility.ControlGallery.Android")] +[assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Android.UITests")] +[assembly: InternalsVisibleTo("Microsoft.Maui.Controls.iOS.UITests")] +[assembly: InternalsVisibleTo("WinUI.UITests")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility.ControlGallery.Tizen")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Xaml")] [assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Xaml.UnitTests")]