Is it possible to wait for Device.BeginInvokeOnMainThread code to finish

Richard0Richard0 Member ✭✭

Hello everyone,

In my code I have a task called "ShowMessageBoxAsync". I want to use this code to show (and await) the DisplayAlert to the user and return the result. Like this: var messageBoxResult = await View.ShowMessageBoxAsync("This is an error");

The code for the ShowMessageBoxAsync is:

    public async System.Threading.Tasks.Task<bool> ShowMessageBoxAsync(string message)
    {
        var result = false;
        Device.BeginInvokeOnMainThread(async () =>
        {
            result = await DisplayAlert("Error", message, "OK", "Cancel");
        });
        return result;
    }

Before I added the Device.BeginInvokeOnMainThread, the task gave me an Exception that it wasn't running on the main/UI thread. So after adding BeginInvokeOnMainThread, it started to work without exceptions. The problem, however, is that the code goes directly to the result, without waiting for the result of the "await DisplayAlert".

Is it possible return the value of "result" only after the Device.BeginInvokeOnMainThread code finishes?

Thanks in advance!

Answers

  • Richard0Richard0 Member ✭✭

    Thank you for your reply.

    I wrapped the DisplayAlert in a new task and tcs gets signaled as soon as there is a result from the DisplayAlert. The problem is that it still goes directly to the result, without waiting for the user input on the DisplayAlert.. This is how it looks like now:

        public async Task<bool> ShowMessageBoxAsync(string message)
        {
            TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
            Task.Factory.StartNew(() =>
            {
                Device.BeginInvokeOnMainThread(async () =>
                {
                    result = await DisplayAlert("Error", message, "OK", "Cancel");
                });
    
                tcs.SetResult(result);
            });
    
            return await tcs.Task;
        }
    

    I added the full project to the attachments.

  • JohnHardmanJohnHardman GBUniversity mod

    @Richard0

    Move the tcs.SetResult(result); to immediately after the result = await DisplayAlert("Error", message, "OK", "Cancel");

    Note that IMHO this is still not the way to structure this code.

  • Richard0Richard0 Member ✭✭
    edited February 7

    @JohnHardman Thank you for your reply. After moving the tcs.SetResult(result), the DisplayAlert doesn't show up at all. I added the project to the attachments.

    This is how the task looks like now:

        public async Task<bool> ShowMessageBoxAsync(string message)
        {
            TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
            Task.Factory.StartNew(() =>
            {
                Device.BeginInvokeOnMainThread(async () =>
                {
                    result = await DisplayAlert("Error", message, "OK", "Cancel");
                    tcs.SetResult(result);
                });
            });
    
            return await tcs.Task;
        }
    
  • JohnHardmanJohnHardman GBUniversity mod
    edited February 7

    @Richard0 said:
    @JohnHardman Thank you for your reply. After moving the tcs.SetResult(result), the DisplayAlert doesn't show up at all. I added the project to the attachments.

    This is how the task looks like now:

        public async Task<bool> ShowMessageBoxAsync(string message)
        {
            TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
            Task.Factory.StartNew(() =>
            {
                Device.BeginInvokeOnMainThread(async () =>
                {
                    result = await DisplayAlert("Error", message, "OK", "Cancel");
                    tcs.SetResult(result);
                });
            });
            
            return await tcs.Task;
        }
    

    That's because the UI thread is being blocked. This is what happens when you try to handle events in a procedural way. I refer you back to the other thread, where this has already been said.

  • Richard0Richard0 Member ✭✭
    edited February 7

    From what I understand is that tcs.SetResult(result); blocks the UI thread. But is there any possibility in the current construction of the code to show a Dialog (without blocking the UI) and only proceed as soon as the user clicks Yes / No?

    Or can this only be solved by splitting it up (for example through a MessageCenter)?

  • KSTeixeiraKSTeixeira USMember ✭✭

    The following code does exactly what you want

    private void Btn_Clicked(object sender, EventArgs e)
    {
          Device.BeginInvokeOnMainThread(async () =>
          {
              bool result = await this.GetResultFromAlert();
              await this.DisplayAlert("Result", result ? "Result = Ok" : "Result = Cancel", "Close");
          });
     }
    
     private async Task<bool> GetResultFromAlert()
     {
         return await this.DisplayAlert("Test", "Message", "Ok", "Cancel");
     }
    

    P.S.: BeginInvokeOnMainThread is added in Btn_Clicked function in order to make both alerts run properly. Another option would be adding BeginInvokeOnMainThread for each of them, but, why duplicate code? :smile:

  • Richard0Richard0 Member ✭✭

    @KSTeixeira Many thanks for your response, it's very helpful :) In order not to break the rest of the project code, the GetResult method must unfortunately always return a bool. This bool result will be based on the result of the DisplayAlert. I tried doing it on this way:

        public bool GetResult()
        {
            bool result = false;
            Device.BeginInvokeOnMainThread(async () =>
            {
                result = await this.GetResultFromAlert("Error");
            });
            return result; // this should return when there is a result from the DisplayAlert
        }
    

    I know what @JohnHardman said about calling Device.BeginInvokeOnMainThread, so this code directly goes to "return result" (without awaiting the DisplayAlert). Do you know if there's a workaround to keep this code in the bool GetResult method, but also await the DisplayAlert result and then return this result?

  • KSTeixeiraKSTeixeira USMember ✭✭

    Got it. Honestly, what you are doing is definitely a "workaround", but i suggest you to stop and think how to solve it in a proper way like allowing the async grows through your application (here is the reference for it, i highly recommend you to read it: https://blogs.msdn.microsoft.com/lucian/2011/04/15/async-ctp-refresh-design-changes/).

    I understand that sometimes we face challenges like this and we "suffer" to release something working, but we MUST do everything we can to keep it consistent and correct, otherwise, you can have even more issues in the future.

    I'm sorry not being able to help you solving your problem, but consider my opinion of refactoring your code. Refactoring is cool man :smile:

  • JohnHardmanJohnHardman GBUniversity mod

    @Richard0 said:
    I know what @JohnHardman said about calling Device.BeginInvokeOnMainThread, so this code directly goes to "return result" (without awaiting the DisplayAlert). Do you know if there's a workaround to keep this code in the bool GetResult method, but also await the DisplayAlert result and then return this result?

    Switching from procedural code to event-driven code involves a change of mindset. In this particular scenario, rather than asking how to make something happen and return Task<bool>, instead consider what you want to happen if the user hits "OK" and what you want to happen if the user hits "Cancel". It's then a case of wiring up the result of DisplayAlert to those difference outcomes, normally without the Task<bool> return (or in the scenario where there is a requirement to implement an interface returning a dummy value). Using MessagingCenter is one way of doing that wiring, although not the only way.

  • Richard0Richard0 Member ✭✭

    @KSTeixeira and @JohnHardman Thank you both for your help!

    I don't have the solution yet, but I've learned new things about async programming. I am going to see how I can refactor the code with your advice. And once I've found a good way, I'll post it here, so that someone else might also benefit from it :)

Sign In or Register to comment.