-1

I have this async function:

private async Task<bool> ShowAboutToMatchAlert()
{
    bool res = true;

    if(Preferences.Get("WantSeeFirstMatchAlertAgain", true))
    {
        Device.BeginInvokeOnMainThread(async () =>
        {
            bool answer = await DisplayAlert("...");

            if (answer)
            {
                Preferences.Set("WantSeeFirstMatchAlertAgain", false);
                res = true;
            }
            else
            {
                res = false;
            }
        });
    }

    return res; 
}

What happens is this functions shows an alert. As soon as the user picks an option (yes or no) the if(answer) is fired and depending on the input, the result is given.

However, the whole function does not wait for the result and immediately skips forward to the final return. So always true is being returned (as the function is async).

The function itself has to be async in order for it to be awaited since this function is also called from another async function.

How can I make this function await the if part and not just skip forward?

Simplest solution of course would be to make this function be not async but then I cannot return "true" or "false" anymore but have to return System.Threading.Task.Task<bool>. So that is not an option.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
inno
  • 376
  • 2
  • 15
  • 2
    I think this call should be awaited: `Device.BeginInvokeOnMainThread` – Michał Turczyn Nov 03 '20 at 10:34
  • What type is your `Device` variable? If it is your own type, can you show what `BeginInvokeOnMainThread` does? – SomeBody Nov 03 '20 at 10:34
  • Device is from XAMARIN.FORMS. It is a crossplattform app. The alert HAS to be invoked on main thread. – inno Nov 03 '20 at 10:36
  • @MichałTurczyn Device.BeginInvokeOnMainThread is returning void. how would I wait for this? – inno Nov 03 '20 at 10:36
  • 1
    Try using `await Device.InvokeOnMainThreadAsync( ... )` – Michał Turczyn Nov 03 '20 at 10:38
  • Alternatively, you can wrap entire method inside `BeginIvoke...` method call. – Michał Turczyn Nov 03 '20 at 10:39
  • 1
    ^^ https://learn.microsoft.com/en-us/dotnet/api/xamarin.forms.device.invokeonmainthreadasync?view=xamarin-forms – Fildor Nov 03 '20 at 10:40
  • 1
    Is this an _XY_ problem? Why does `DisplayAlert` need to be `async`? If it's just a modal popup dialog surely it should be synchonous? –  Nov 03 '20 at 10:45
  • Because you are returning `res` you need to `await Device.BeginInvokeOnMainThread`. Norrmally it is fine to have _fire-and-forget_ `Tasks` but not when the method depends on the result –  Nov 03 '20 at 10:47
  • @MichałTurczyn i cant wait for void and i cannot wrap the whole method because at least the final return needs to be outside of the braces. therfore nothing is awaited in this function. – inno Nov 03 '20 at 10:49
  • @MickyD since i need to wait for the user input here, i have to await the alert being closed to work with the result (ANSWER). I cannot await Device.BeginInvokeOnMainThread because it returns void – inno Nov 03 '20 at 10:50
  • you don´t need `await` to wait for a modal dialog. The point of a modal dialog is that execution only continues when there was some user-action. – MakePeaceGreatAgain Nov 03 '20 at 10:52
  • @HimBromBeere without the await, it says cannot convert system.threading.tasks.task into bool – inno Nov 03 '20 at 10:54
  • then please show your `DisplayAlert`-method. I doubt it should be `async` in the first place. – MakePeaceGreatAgain Nov 03 '20 at 10:55
  • @HimBromBeere it is coming from xamarin.forms. i just followed the docs here https://forums.xamarin.com/discussion/149889/is-it-possible-to-wait-for-device-begininvokeonmainthread-code-to-finish – inno Nov 03 '20 at 10:58
  • inno the [`Device.InvokeOnMainThreadAsync`](https://learn.microsoft.com/en-us/dotnet/api/xamarin.forms.device.invokeonmainthreadasync) method suggested by MichałTurczyn does not return `void`. It returns a `Task`. – Theodor Zoulias Nov 03 '20 at 12:17

1 Answers1

0

If Device.BeginInvokeOnMainThread returns void and cannot be awaited - you can use TaskCompletionSource to achieve your goal. This class allows you to obtain Task (which can be awaited) and manually set it's completion with result. Your code then becomes:

private Task<bool> ShowAboutToMatchAlert()
{
    var tcs = new TaskCompletionSource<bool>();

    if(Preferences.Get("WantSeeFirstMatchAlertAgain", true))
    {
        Device.BeginInvokeOnMainThread(async () => {
            bool answer = await DisplayAlert("...");

            if (answer)
            {
                Preferences.Set("WantSeeFirstMatchAlertAgain", false);
                // signal completion with "true" result
                tcs.SetResult(true);
            }
            else
            {
                // signal completion with "false" result
                tcs.SetResult(false);
            }
        });
    }
    else {
        tcs.SetResult(false);
    }

    return tcs.Task;
}

Beware that tcs.SetResult (or SetCancelled or SetException) should be called on all code paths, including exceptions, for example potentially produced by DisplayAlert (not shown in the above example), otherwise you can get into nasty deadlocks.

Evk
  • 98,527
  • 8
  • 141
  • 191