I would like to run an async method synchronously when the async function causes a dialog to show on the UI?

Congratulations, you have found one of the corner cases where there is no solution. There are hacks for sync-over-async that work in some cases, but no solution will work here.

Here's why no known solutions will work:

  • You can't directly block because CheckAndSaveData needs to run a UI loop.
  • You can't block on a thread pool thread because CheckAndSaveData interacts with the UI.
  • You can't use a replacement single-threaded SynchronizationContext (as in the incorrectly linked duplicate), because CheckAndSaveData needs to pump Win32 messages.
  • You can't even use a nested UI loop (Dispatcher Frame in this case), because pumping those same Win32 messages will cause your command to be executed.

Put more simply:

  • CheckAndSaveData must pump messages to show a UI.
  • CheckAndSaveData cannot pump messages to prevent the command from executing.

So, there is no solution here.

Instead, you'll need to modify your command so that it waits for CheckAndSaveData somehow, either using a synchronization primitive or by just calling CheckAndSaveData directly from the command.


An option is to use SemaphoreSlim in your unfocus event and your command event to make the command event wait until the unfocus event is complete.

When a part of your code enters the semaphore (using Wait or WaitAsync), it will block other parts of your code that try to enter the semaphore until the semaphore is released.

Here's an example of what that could look like:

private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1,1);

private async void OnEntryLosesFocus(object sender, EventArgs e) {
    var vm = (MainPageViewModel)BindingContext;
    if (vm == null)
    {
        return;
    }
    if (!IsAnyNavButtonPressedOnUWP())
    {
        try {
            await _semaphore.WaitAsync();
            
            // CheckAndSaveData() causes dialogs to show on the UI.
            // I need to ensure that CheckAndSaveData() completes its execution and control is not given to another function while it is running.
            _saveSuccessfulInUnfocus = await vm.CheckAndSaveData();
            if (_saveSuccessfulInUnfocus)
            {
                if (Device.RuntimePlatform == Device.UWP)
                {
                    if (_completedTriggeredForEntryOnUWP)
                    {
                        var navAction = vm.GetNavigationCommand();
                        navAction?.Invoke();
                        _completedTriggeredForEntryOnUWP = false;
                    }
                }
                else 
                {
                    vm._stopwatchForTap.Restart();
                }
            }
            else
            {
                vm._stopwatchForTap.Restart();
            }
        }
        finally
        {
            _semaphore.Release();
        }
    }    
}

private async void OnCommand(object sender, EventArgs e) {
    try {
        // Execution will wait here until Release() is called in OnEntryLosesFocus
        await _semaphore.WaitAsync();
        
        // do stuff
    }
    finally
    {
        _semaphore.Release();
    }
}

The try/finally blocks are optional, but it helps make absolutely sure that the semaphore is released even if an unhandled exception happens.