Is it possible to call an asynchronous method from a synchronous method?

Richard0Richard0 Member ✭✭
edited January 31 in Xamarin.Forms

Hello everyone,

I'm having a Xamarin.forms application and what I'm trying to do is:
1) someone clicks on an item in a listview
2) the application shows a dialog to the user
3) the application waits until the user has pressed "yes" or "no"
4) as soon as there's input from the user, the application (code) continues again

I know that normally it's possible to make your method async and then apply an await on the DisplayAlert. However, because the code is so enormous, changing the method to async has a lot of effect on the rest of my code.. So I wondered if it's possible to call/await an asynchronous method from a synchronous method?

To show the dialog, I use the following code:

private async Task<bool> ShowDialog()
{
return await DisplayAlert("Question?", "Waiting for user input", "Yes", "No");
}

And to await the message box I use the following the code:

public void SyncMethod()
{
// Showing the listview here
// The user clicks an item
bool result = await ShowDialogAsync();

// after the user has clicked something,
// then continue with more code here.
if (result)
// Result = true
}

Unfortunately the SyncMethod needs to be async. Is there a way to solve this without making this method async? Thanks for the answer :)

Answers

  • deczalothdeczaloth DEMember ✭✭✭
    edited January 31

    What about something like

    Task.Run(async () => await MyAsyncMethod());

    But that is actually the same as just calling your async method without awaiting, and i am afraid then the rest of the code will not wait until the user acts on the dialog.

    Other way is to use

    var result = Task.Run(async () => await MyAsyncMethod()).Result;

    or simply

    var result = MyAsyncMethod().Result;

    But that will block your main thread (not a Best Practice in most use cases).

    Read this: https://blog.xamarin.com/getting-started-with-async-await/

    (sorry for the multiple editions)

  • Richard0Richard0 Member ✭✭
    edited January 31

    Thank you for your reply. The first option doesn't give any exceptions, but it doesn't show the DisplayAlert either :( Should I perhaps change the way how the dialog is shown?

    Edit: the .Result version causes:
    System.AggregateException: One or more errors occurred.

  • deczalothdeczaloth DEMember ✭✭✭

    How exactly looks your routine? and what are the errors (down the stacktrace should appear the original error)?

  • Richard0Richard0 Member ✭✭
    edited January 31

    I reproduced the problem in a small project, so what I did was create a simple project with one button. This button click event activates the SyncMethod (from the first post). I re-wrote the await/async to a Task.

    Task.Factory.StartNew(() =>
    {
    Device.BeginInvokeOnMainThread(async () => await ShowDialog());
    }).ContinueWith(task =>
    {
    Debug.WriteLine("The user pressed something on the dialog");
    }, TaskScheduler.FromCurrentSynchronizationContext());

    As soon as I start this, it will run the Debug.WriteLine("The user pressed something on the dialog"); directly. What I don't understand is that - even though I'm using ContinueWith, the Debug.WriteLine runs immediately, without waiting for ShowDialog() to finish .. Am I missing something?

  • JohnHardmanJohnHardman GBUniversity mod
    edited January 31

    @Richard0 said:
    As soon as I start this, it will run the Debug.WriteLine("The user pressed something on the dialog"); directly. What I don't understand is that - even though I'm using ContinueWith, the Debug.WriteLine runs immediately, without waiting for ShowDialog() to finish .. Am I missing something?

    I haven't tested this, but give this a try. It looks like you have the ContinueWith in the wrong place for what you are asking:

    Task.Run(() =>
    {
        Device.BeginInvokeOnMainThread(
            async () =>
            {
                await ShowDialog()
                    .ContinueWith(
                        task => 
                        { 
                            Debug.WriteLine("The user pressed something on the dialog"); 
                        },
                        TaskScheduler.FromCurrentSynchronizationContext());
            });
    });
    
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Personally, I'm old-school and like separating my workflow to small, discrete portions that have 1 purpose.
    When you start writing these long routines where you have one big method that tries to do it all, it becomes hard to manage and hard to insert changes between steps and so on.

    For that reason I'm still a big fan of the callback methodology.

    I have a portion of code that does a job. If it raises a dialog for "please rescan your image" or whatever then its job is done once the dialog goes up. The dialog then throws out a MessageCenter message when it closes. Your view model has a handler for the message to do the task from that point.

    At first this seems a little overkill. Until you've had a program live in the field for a while. 6 months later the boss says "We need to insert a new process between "C" and "D" then carry on like have been. If you've got a long method then you have to wedge it in and hope it doesn't break working code. If its all little lego blocks of separated concerns then the "D" portion of the workflow doesn't get touched. You add in your "C and a half" new step. But everything that is coded to respond to the messages from the "C" dialog and the "D" dialog remain pristine. No new QA required, no regression testing for those stages etc.

  • Richard0Richard0 Member ✭✭
    edited January 31

    @JohnHardman Thank you! That already helped a lot, Debug.WriteLine now gets executed after a button is pressed. However, this has to be in a bool method (due to the original code). Like this:

    public bool SyncMethod()
    {
    bool result = false;
    var MessageboxTask = Task.Run(() =>
    {
    Device.BeginInvokeOnMainThread(
    async () => { await ShowDialog().ContinueWith(task =>
    {
    result = task.Result;
    },
    TaskScheduler.FromCurrentSynchronizationContext());
    });
    });
    MessageboxTask.Wait();
    return result;
    }

    Normally this would force to wait until the user clicks Yes or No, and then return the result (using .Wait()), right? For example when the user clicks "Yes", this method returns true. But in this case it always goes directly to "return result;" (without waiting for the task) and the result is always false.

  • Richard0Richard0 Member ✭✭

    @ClintStLaurent Thank you for your reply! I totally agree on splitting it up and using a MessageCenter message instead, unfortunately the client thinks otherwise :D

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Honestly... Why does the client even know how you're doing it? Aren't they paying for an application? Why are they architecting your code for you?

    I did freelance for a couple decades. Rarely did my client know boo of the design. They tell me what they want for features. Looks. Maybe some UI mock-ups. Explanation of what their backend integration requires. That's it. I give them a breakdown of milestones that they will confirm and the milestone payment that goes with them signing off on that portion of the app being "as requested". But beyond that... rarely has anyone cared about whether it was MVVM or PRISM or EntityFramework blah blah blah.

    If your client is getting that deep into your code... watchout. Once they have seen/received a large enough portion you're going to get cut and their Mumbai outsourcers are going to do the rest for 1/5th the cost and 1/10th the skill. Then you'll get contacted again to fix their total cluskter-f*** of a code base.

  • NMackayNMackay GBInsider, University mod

    @ClintStLaurent said:
    Honestly... Why does the client even know how you're doing it? Aren't they paying for an application? Why are they architecting your code for you?

    I did freelance for a couple decades. Rarely did my client know boo of the design. They tell me what they want for features. Looks. Maybe some UI mock-ups. Explanation of what their backend integration requires. That's it. I give them a breakdown of milestones that they will confirm and the milestone payment that goes with them signing off on that portion of the app being "as requested". But beyond that... rarely has anyone cared about whether it was MVVM or PRISM or EntityFramework blah blah blah.

    If your client is getting that deep into your code... watchout. Once they have seen/received a large enough portion you're going to get cut and their Mumbai outsourcers are going to do the rest for 1/5th the cost and 1/10th the skill. Then you'll get contacted again to fix their total cluskter-f*** of a code base.

    That does sound an awfully familiar scenario! :'(

  • JohnHardmanJohnHardman GBUniversity mod

    @ClintStLaurent said:
    Honestly... Why does the client even know how you're doing it? Aren't they paying for an application? Why are they architecting your code for you?

    I did freelance for a couple decades. Rarely did my client know boo of the design. They tell me what they want for features. Looks. Maybe some UI mock-ups. Explanation of what their backend integration requires. That's it. I give them a breakdown of milestones that they will confirm and the milestone payment that goes with them signing off on that portion of the app being "as requested". But beyond that... rarely has anyone cared about whether it was MVVM or PRISM or EntityFramework blah blah blah.

    If your client is getting that deep into your code... watchout. Once they have seen/received a large enough portion you're going to get cut and their Mumbai outsourcers are going to do the rest for 1/5th the cost and 1/10th the skill. Then you'll get contacted again to fix their total cluskter-f*** of a code base.

    Similarly, couple of decades of freelancing. In that time I did have a couple of clients who micro-managed how code was written, in one case to such an extent that I didn't recognise the result as being my own code once it was complete - nothing like how I would normally write code. I didn't renew that contract - might as well have been a machine...

  • JohnHardmanJohnHardman GBUniversity mod
    edited January 31

    @Richard0 said:
    @JohnHardman Thank you! That already helped a lot, Debug.WriteLine now gets executed after a button is pressed. However, this has to be in a bool method (due to the original code). Like this:

    public bool SyncMethod()
    {
    bool result = false;
    var MessageboxTask = Task.Run(() =>
    {
    Device.BeginInvokeOnMainThread(
    async () => { await ShowDialog().ContinueWith(task =>
    {
    result = task.Result;
    },
    TaskScheduler.FromCurrentSynchronizationContext());
    });
    });
    MessageboxTask.Wait();
    return result;
    }

    Normally this would force to wait until the user clicks Yes or No, and then return the result (using .Wait()), right? For example when the user clicks "Yes", this method returns true. But in this case it always goes directly to "return result;" (without waiting for the task) and the result is always false.

    In the code you posted there, it will do the Wait(). However, you're waiting on the wrong thing again. Use the debugger to put a breakpoint on return result; and have a look at MessageboxTask.

    Three comments:
    (1) I recommend doing the Xamarin University classes related to threads/tasks/async/await, or at least working through a good book on the subject (or some online blogs on the subject).
    (2) @ClintStLaurent is correct about splitting things up. Whichever tech you choose to use, this sort of code ideally wants to be event driven, rather than trying to shoehorn UI interactions into a sequential block of code.
    (3) Be very wary of Wait().

  • KSTeixeiraKSTeixeira USMember ✭✭

    Task.Run is "Fire and forget", so take care here. But, honestly, I don't see any problem adding the entire code inside Task.Run(() => { ... }) as long as you don't execute anything else after this execution. BeginInvokeOnMainThread will only be necessary when calling any thing that refers to UI piece.

    About MessaginCenter, think twice when choosing this approach. @AdamPedley wrote an excellent post about MessagingCenter: https://xamarinhelp.com/common-misuse-messagingcenter/

    Remember: This is an opinion based on you current code, the ideal thing would be what @ClintStLaurent suggested about spliting things up.

  • Richard0Richard0 Member ✭✭

    Thanks for the replies everyone! @KSTeixeira I used your advice to put all the code in the Task.Run

    The problem is that this method has to be implemented in older (existing) code, so the client wants the "GetResultSync" to return only a bool. This unfortunately limits me in my implementation choices..

    I changed my code to this now:

        public bool GetResultSync()
        {
            bool result = false;
            return Task.Run(() =>
            {
                Device.BeginInvokeOnMainThread(
                    async () =>
                    {
                        await ShowDialog().ContinueWith( // Start this code after the user pressed something on the dialog
                            task =>
                            {
                                result = task.Result; // Set the result based on what the user chose
                            },
                        TaskScheduler.FromCurrentSynchronizationContext());
                    });
                return result;
            }).Result; // Wait until there is a result and then return this method
    
        }
    

    This calls the method and writes the result to the output
    private void Button_Clicked(object sender, EventArgs e)
    {
    Debug.WriteLine("The user pressed: " + GetResultSync());
    }

    I'd think that returning .Result should always wait until the user pressed Yes or No, right? However as soon as I press the button, the result is always directly false, even before the user presses Yes or No.

  • JohnHardmanJohnHardman GBUniversity mod

    @Richard0

    The thread calling Device.BeginInvokeOnMainThread does not wait for the code being invoked on the main thread to complete. Hence the return result; executes before result = task.Result;

    As per my previous post:

    (1) I recommend doing the Xamarin University classes related to threads/tasks/async/await, or at least working through a good book on the subject (or some online blogs on the subject).
    (2) @ClintStLaurent is correct about splitting things up. Whichever tech you choose to use, this sort of code ideally wants to be event driven, rather than trying to shoehorn UI interactions into a sequential block of code.

  • KSTeixeiraKSTeixeira USMember ✭✭

    Go as suggested by @JohnHardman , take a look on classes related do async programming, it'll help you a lot. After that,i suggest you to take a look on this post, it's very interesting for what you need.

    https://cpratt.co/async-tips-tricks/

Sign In or Register to comment.