简体   繁体   English

我可以从OnOptionsItemSelected调用异步方法吗?

[英]Can I call an async method from OnOptionsItemSelected?

I'm teaching myself to use the Xamarin Platform (I'm also new to C#). 我正在教自己使用Xamarin平台(我也是C#的新手)。 I'm creating an app where the user logs on and is forced to create a profile. 我正在创建一个用户登录的应用程序,并被迫创建一个配置文件。 I'd like to have the ActionBar (I'm using the material toolbar) contain a menu item DONE . 我想让ActionBar(我正在使用材料工具栏)包含一个菜单项DONE When the user clicks DONE , my app verifies the input data and sends the data to my Parse backend. 当用户单击DONE ,我的应用程序将验证输入数据并将数据发送到Parse后端。 The problem with this is that the Parse API requires an await profile.SaveAsync(); 这个问题是Parse API需要await profile.SaveAsync(); in order to do this. 为此。 In order to determine that the user clicked DONE in the ActionBar, I need to override OnOptionsItemSelected(IMenuItem item) , which is not asynchronous. 为了确定用户在ActionBar中单击了DONE ,我需要覆盖OnOptionsItemSelected(IMenuItem item) ,这不是异步的。

I've found a way around this by creating private async void ActionDone() that will handle all the Parse connections. 我已经通过创建private async void ActionDone()来找到解决方法,它将处理所有Parse连接。 I then call ActionDone() within my switch statement in OnOptionsItemSelected . 然后我在OnOptionsItemSelected switch语句中调用ActionDone() However, I think this is tying up the UI thread with await . 但是,我认为这是在await用户界面线程。 I've read that this is a bad idea (mostly on other StackOverflow posts). 我已经读过这个坏主意(主要是在其他StackOverflow帖子上)。 Is there another way to get the ActionBar to await? 还有另一种让ActionBar等待的方法吗? Or am I safe because in order to proceed, the Profile needs to be saved and therefore holding up the UI is "acceptable"? 或者我是否安全,因为为了继续,需要保存配置文件,因此保持UI是“可接受的”?

OnOptionsItemSelected 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 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 parse calls are in a shared library so I'll be able to use them with iOS as well 我的所有解析调用都在共享库中,因此我也可以在iOS中使用它们

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've already implemented the code this way, and it works (as far as I can tell). 我应该补充一点,我已经以这种方式实现了代码,并且它可以工作(据我所知)。 Just based on what I've read, I'm thinking it's not the best strategy. 根据我所读到的内容,我认为这不是最好的策略。

In response to your comment: 回应你的评论:

do you mean that the return in OnOptionsItemSelected() has the possibility to execute before the await finishes 你的意思是OnOptionsItemSelected()中的返回有可能在await结束之前执行

Yes. 是。 In fact, that is the likely outcome (ie you intend for the operation to be asynchronous, so the typical case is for the operation to complete asynchronously). 实际上,这可能是结果(即您希望操作是异步的,因此典型的情况是操作异步完成)。

Or am I safe because in order to proceed, the Profile needs to be saved and therefore holding up the UI is "acceptable"? 或者我是否安全,因为为了继续,需要保存配置文件,因此保持UI是“可接受的”?

I would not say that blocking the UI thread is ever acceptable. 我不会说,阻断UI线程是有史以来接受的。 Granted, it can be simpler to implement: whenever you have asynchronous operations while the UI thread is free to execute, then you have to worry about the state of the UI, whether the user can click on or otherwise issue commands that may or may not be valid while the asynchronous operation is in progress, etc. 当然,它可以更简单地实现:只要您在UI线程可以自由执行时进行异步操作,那么您必须担心UI的状态,用户是否可以单击或以其他方式发出可能会或可能不会发出的命令在异步操作正在进行时有效等。

But frankly, that's exactly the kind of issues that the async / await feature is designed to make a lot easier. 但坦率地说,这正是async / await功能旨在使事情变得更容易的问题。 You can write code in a linear way that reconfigures the UI for the duration of an asynchronous operation (if needed) and then just as easily put things back when it's done. 您可以以线性方式编写代码,在异步操作(如果需要)的持续时间内重新配置UI,然后在完成后轻松地将其放回原处。


As your code is written now, the asynchronous operation will not block the UI thread. 在您的代码现在编写时,异步操作不会阻止UI线程。 But the OnOptionsItemSelected() method does return, first calling the base implementation, before that operation has completed. 但是在该操作完成之前, OnOptionsItemSelected()方法确实返回,首先调用基本实现。

Whether this is a problem in your case, I don't know. 在你的情况下这是否是一个问题,我不知道。 There's not enough context here. 这里没有足够的背景。 But… 但…

The only other action in the method is to call the base implementation and return that implementation's result. 该方法中唯一的其他操作是调用基本实现并返回该实现的结果。 As long as there is nothing in that base implementation that would depend on the outcome of the asynchronous operation, and as long as returning the base implementation's return value from the method before the asynchronous operation has completed doesn't mislead the caller (and if the base implementation doesn't depend on the asynchronous operation, I would think it wouldn't), it should be fine. 只要该基本实现中没有任何内容依赖于异步操作的结果,并且只要在异步操作完成之前从方法返回基本实现的返回值,就不会误导调用者(如果基本实现不依赖于异步操作,我认为不会),应该没问题。

If the base implementation does depend on the outcome of the asynchronous operation, you can wrap the whole method body in an async method so that you can await the asynchronous operation and defer calling of the base implementation until the operation has completed. 如果基本实现依赖于异步操作的结果,则可以将整个方法体包装在async方法中,以便await异步操作并推迟调用基本实现,直到操作完成。 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'll note that in the above, the actual overridden method still has to return some value. 但是你会注意到,在上面,实际重写的方法仍然需要返回一些值。 In other words, you wind up having to lie to the caller, and (optionally) having to deal with the actual outcome of the base implementation later. 换句话说,你不得不欺骗调用者,并且(可选地)以后必须处理基本实现的实际结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM