Skip to content

Commit 9e23a83

Browse files
committed
fix(Maui): marshal modal dialog push/pop on UI dispatcher
DialogContainerPage runs PushModalAsync/PopModalAsync and DialogStack updates via IDispatcher.DispatchAsync when dispatch is required; run inline when already on the UI thread or when no dispatcher (avoids nested DispatchAsync deadlock during re-entrant close). Consolidated null/IsDispatchRequired guard per review.
1 parent 37eab54 commit 9e23a83

1 file changed

Lines changed: 31 additions & 7 deletions

File tree

src/Maui/Prism.Maui/Dialogs/DialogContainerPage.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
using System.ComponentModel;
1+
using System.ComponentModel;
22
using System.Windows.Input;
33
using Microsoft.Maui.Controls.PlatformConfiguration;
44
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
5+
using Microsoft.Maui.Dispatching;
56
using Microsoft.Maui.Layouts;
67
using Prism.Dialogs.Xaml;
78
using Application = Microsoft.Maui.Controls.Application;
@@ -36,6 +37,23 @@ public DialogContainerPage()
3637
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.OverFullScreen);
3738
}
3839

40+
static IDispatcher? GetDialogDispatcher(Page currentPage) =>
41+
currentPage.Dispatcher ?? Application.Current?.Dispatcher;
42+
43+
static async Task DispatchModalAsync(Page currentPage, Func<Task> work)
44+
{
45+
var dispatcher = GetDialogDispatcher(currentPage);
46+
// No dispatcher (e.g. some test hosts), or already on the UI thread—including re-entrant DoPop during
47+
// PushModalAsync: run inline so we do not nest DispatchAsync and deadlock the dispatcher queue.
48+
if (dispatcher is null || !dispatcher.IsDispatchRequired)
49+
{
50+
await work().ConfigureAwait(true);
51+
return;
52+
}
53+
54+
await dispatcher.DispatchAsync(work).ConfigureAwait(false);
55+
}
56+
3957
/// <summary>
4058
/// Gets the dialog view displayed in the container page.
4159
/// </summary>
@@ -72,11 +90,14 @@ public async Task ConfigureLayout(Page currentPage, View dialogView, bool hideOn
7290
/// <returns>A task representing the asynchronous operation.</returns>
7391
protected virtual async Task DoPush(Page currentPage)
7492
{
75-
await currentPage.Navigation.PushModalAsync(this, false);
76-
if (_closedBeforeOrDuringPush)
77-
return;
93+
await DispatchModalAsync(currentPage, async () =>
94+
{
95+
await currentPage.Navigation.PushModalAsync(this, false);
96+
if (_closedBeforeOrDuringPush)
97+
return;
7898

79-
IDialogContainer.DialogStack.Add(this);
99+
IDialogContainer.DialogStack.Add(this);
100+
});
80101
}
81102

82103
/// <summary>
@@ -87,8 +108,11 @@ protected virtual async Task DoPush(Page currentPage)
87108
public virtual async Task DoPop(Page currentPage)
88109
{
89110
_closedBeforeOrDuringPush = true;
90-
await currentPage.Navigation.PopModalAsync(false);
91-
IDialogContainer.DialogStack.Remove(this);
111+
await DispatchModalAsync(currentPage, async () =>
112+
{
113+
await currentPage.Navigation.PopModalAsync(false);
114+
IDialogContainer.DialogStack.Remove(this);
115+
});
92116
}
93117

94118
/// <summary>

0 commit comments

Comments
 (0)