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

本文关键字:异步方法 调用 OnOptionsItemSelected 我可以 | 更新日期: 2023-09-27 18:00:55

我正在自学使用Xamarin平台(我也是C#的新手(。我正在创建一个应用程序,用户登录该应用程序并被迫创建配置文件。我希望ActionBar(我使用的是材质工具栏(包含一个菜单项DONE。当用户单击DONE时,我的应用程序会验证输入数据,并将数据发送到我的Parse后端。这样做的问题是Parse API需要一个await profile.SaveAsync();才能做到这一点。为了确定用户在ActionBar中单击了DONE,我需要覆盖OnOptionsItemSelected(IMenuItem item),它不是异步的。

我已经找到了解决这个问题的方法,创建了处理所有Parse连接的private async void ActionDone()。然后,我在OnOptionsItemSelected中的switch语句中调用ActionDone()。但是,我认为这会占用await的UI线程。我读到这是一个坏主意(主要是在其他StackOverflow帖子中(。还有其他方法可以让ActionBar等待吗?或者我是否安全,因为为了继续,需要保存配置文件,因此保持UI是"可以接受的"?

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();
        }
    }

我所有的解析调用都在一个共享库中,所以我可以在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;
    }

我应该补充一点,我已经用这种方式实现了代码,而且它很有效(据我所知(。根据我读到的内容,我认为这不是最好的策略。

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

回复您的评论:

你的意思是说OnOptionsItemSelected((中的返回有可能在等待完成之前执行吗

是的。事实上,这就是可能的结果(即,您希望操作是异步的,因此典型的情况是操作异步完成(。

或者我是否安全,因为为了继续,需要保存配置文件,因此保持UI是"可以接受的"?

我不会说阻塞UI线程是可以接受的。诚然,它可以更简单地实现:每当你在UI线程可以自由执行的情况下进行异步操作时,你就必须担心UI的状态,用户是否可以在异步操作进行时点击或发出可能有效或无效的命令,等等

但坦率地说,这正是async/await功能的设计目的,目的是让问题变得更容易。您可以以线性方式编写代码,在异步操作期间重新配置UI(如果需要的话(,然后在完成后轻松地将事情放回原处。


现在编写代码时,异步操作不会阻塞UI线程。但是OnOptionsItemSelected()方法确实返回,在该操作完成之前首先调用基本实现。

我不知道这是否是你的问题。这里没有足够的上下文。但是…

该方法中唯一的其他操作是调用基本实现并返回该实现的结果。只要基本实现中没有任何内容依赖于异步操作的结果,并且只要在异步操作完成之前从方法返回基本实现的返回值不会误导调用者(如果基本实现不依赖于异步运算,我认为它不会(,应该没事。

如果基础实现确实依赖于异步操作的结果,则可以将整个方法体封装在async方法中,以便await异步操作,并推迟对基础实现的调用,直到操作完成。例如:

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() { ... }

但您会注意到,在上面的内容中,实际重写的方法仍然需要返回一些值。换句话说,您最终不得不对调用者撒谎,并且(可选地(必须稍后处理基本实现的实际结果。