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

Mat*_*att 5 c# android xamarin

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

我通过创建private async void ActionDone()将处理所有 Parse 连接找到了解决此问题的方法。然后ActionDone()我在我的switch声明中调用OnOptionsItemSelected. 但是,我认为这是将 UI 线程与await. 我读过这是一个坏主意(主要是在其他 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);
    }
Run Code Online (Sandbox Code Playgroud)

行动完成

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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

我所有的解析调用都在一个共享库中,所以我也可以在 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;
    }
Run Code Online (Sandbox Code Playgroud)

我应该补充一点,我已经以这种方式实现了代码,并且它可以工作(据我所知)。仅根据我所读到的内容,我认为这不是最佳策略。

Pet*_*iho 4

回复您的评论:

您的意思是 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() { ... }
Run Code Online (Sandbox Code Playgroud)

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