Can I call async method from OnOptionsItemSelected?

I am learning to use the Xamarin framework (I am new to C # too). I am creating an application where the user logs in and is forced to create a profile. I would like the ActionBar (I am using the material toolbar) to contain a menu item DONE

. When the user clicks the button DONE

, my app validates the input and sends the data to my server. The problem is that it requires the Parse API to await profile.SaveAsync();

require await profile.SaveAsync();

. To determine what the user has clicked DONE

on the ActionBar, I need to override OnOptionsItemSelected(IMenuItem item)

which is not asynchronous.

I found a way to get around this by creating private async void ActionDone()

one that will handle all Parse connections. Then I call ActionDone()

into my statement switch

in OnOptionsItemSelected

. However, I think this binds the UI thread to await

. I read that this is a bad idea (mostly in other StackOverflow posts). Is there any other way to make the ActionBar wait? Or am I sure because the Profile needs to be saved in order to proceed and therefore UI support is "acceptable"?

OnOptionsItemSelected

public override bool OnOptionsItemSelected(IMenuItem item)
    {
        // verify nothing else was clicked
        switch(item.ItemId)
        {
            case Resource.Id.action_done:
                ActionDone();
                break;
            default:
                // log error message
                break;
        }

        return base.OnOptionsItemSelected(item);
    }

      

ActionDone

private async void ActionDone()
    {
        Task<ApiHandler.CreateProfileStruct> createProfileTask = ApiHandler.CreateProfile(mNameEdit.Text, mPhoneEdit.Text, mEmailEdit.Text, mSeriesEdit.Text, mLocationEdit.Text);
        var result = await createProfileTask;

        // toast the result...
        Toast.MakeText(this, result.message, ToastLength.Long).Show();

        // check if profile was created
        if (result.enumValue == ApiHandler.CreateProfileEnum.Success)
        {
            StartActivity(typeof(MainActivity));
            Finish();
        }
    }

      

All my parsing calls are in a shared library, so I can use them with iOS as well

public static async Task<CreateProfileStruct> CreateProfile(string name, string phone, string email, string series, string location)
    {
        Profile profile = new Profile(name, phone, email, series, location);

        CreateProfileStruct result = new CreateProfileStruct();
        string validation = profile.validate();

        // profile information is acceptable...
        if (validation.Equals(""))
        {
            Console.WriteLine("creating profile");

            try
            {
                await profile.SaveAsync();
            }
            catch (ParseException e)
            {
                // set enum to error
                result.enumValue = CreateProfileEnum.Error;

                // determine the error message
                if (e.Code == ParseException.ErrorCode.ConnectionFailed)
                    result.message = parseNoConnection;
                else
                    result.message = profileCreationFailed;

                // return
                return result;
            }

            result.enumValue = CreateProfileEnum.Success;
            result.message = profileCreated;

            // change ParseUser["hasProfile"] to true
            ParseUser user = ParseUser.CurrentUser;
            user["hasProfile"] = true;
            user.SaveAsync();

            return result;
        }
        // profile info is not acceptable
        else
        {
            result.enumValue = CreateProfileEnum.Error;
            result.message = validation;
            return result;
        }
    }

    public enum CreateProfileEnum
    {
        Success,
        Error
    }

    public struct CreateProfileStruct
    {
        public CreateProfileEnum enumValue;
        public string message;
    }

      

I should add that I have already implemented the code this way and it works (as far as I can tell). Based only on what I've read, I think this is not the best strategy.

+3


source to share


1 answer


In response to your comment:

You mean the return in OnOptionsItemSelected () has the ability to execute before the wait is complete

Yes. In fact, this is a likely outcome (i.e. you are assuming the operation will be asynchronous, so the typical case is that the operation is asynchronous).

Or am I sure because in order to proceed, the Profile must be saved and therefore UI support is "acceptable"?

I wouldn't say blocking the UI thread is always acceptable. It is, of course, easier to implement: whenever you have asynchronous operations while the UI thread can be executing, you need to worry about the state of the UI, whether the user can click or otherwise issue commands that may or may not be valid during the execution of an asynchronous operation, etc.

But to be honest, these are exactly the kind of problems that function async

/ is await

meant to make a lot easier. You can write code in a linear fashion that reconfigures the user interface for the duration of the asynchronous operation (if needed) and then just as easily defers everything when it's done.




Since your code is now written, the asynchronous operation will not block the UI thread. But the method OnOptionsItemSelected()

returns by first calling the underlying implementation, before the operation completes.

Whether this is the problem in your case, I don't know. There is a lack of context here. But & hellip;

The only other action in the method is to call the underlying implementation and return the implementation result. As long as there is nothing in this base implementation that could depend on the result of the asynchronous operation, and as long as the return value of the underlying implementation returned from the method before the asynchronous operation completes misleads the caller (and if the underlying implementation does not depend on the asynchronous operation, I would think it isn't), that should be good.

If the base implementation depends on the result of the asynchronous operation, you can wrap the body of the entire method with a method async

so that you can await

perform the asynchronous operation and defer invoking the base implementation until the operation is complete. For example:

public override bool OnOptionsItemSelected(IMenuItem item)
{
    var ignoreTask = OnOptionsItemSelectedAsync(item);

    return true;
}

private async Task OnOptionsItemSelectedAsync(IMenuItem item)
{
    try
    {
        // verify nothing else was clicked
        switch(item.ItemId)
        {
            case Resource.Id.action_done:
                await ActionDone();
                break;
            default:
                // log error message
                break;
        }

        bool result = base.OnOptionsItemSelected(item);

        // If some action should be taken depending on "result",
        // that can go here
    }
    catch (Exception e)
    {
        // The caller is ignoring the returned Task, so you definitely want
        // to observe exceptions here. If you have known exceptions that can
        // be handled reasonably, add an appropriate "catch" clause for that
        // exception type. For any other exceptions, just report/log them as
        // appropriate for your program, and rethrow. Ultimately you'd like
        // that to cause the entire process to crash with an "unobserved task
        // exception" error, which is what you want for an exception you didn't
        // anticipate and had no way to actually handle gracefully. Note that
        // in .NET 4.5 and later, unobserved exceptions WILL NOT crash the process,
        // unless you set the ThrowUnobservedTaskExceptions option to "true"
        // in your App.config file. IMHO, doing so is a VERY good idea.

        throw;
    }
}

// This has to be awaitable...you should eschew "async void"
// methods anyway, so this is a change you'd want to make regardless.
private async Task ActionDone() { ... }

      

But you will notice that in the above, a valid overridden method must still return some value. In other words, you are forced to lie to the caller and (optionally) deal with the actual result of the underlying implementation later.

+1


source







All Articles