简体   繁体   English

如何在不阻塞主UI线程的情况下等待事件处理程序返回值

[英]How to wait for a return value from an event handler without blocking main UI thread

So I have this method this uses a library to generate an external webpage to allow the user to enter their Facebook credentials for verification. 所以我有这个方法,它使用一个库来生成一个外部网页,允许用户输入他们的Facebook凭据进行验证。 Upon entering it we are creating a profile based on the that user's account and saving it to a database. 输入后,我们将根据该用户的帐户创建配置文件并将其保存到数据库中。 Once we save it to the database we are expecting a boolean to represent whether or not the account was unique. 一旦我们将它保存到数据库,我们期望一个布尔值来表示该帐户是否是唯一的。 We need some way to wait for the database to complete all of its work before continuing execution. 我们需要一些方法来等待数据库在继续执行之前完成所有工作。

I've tried several different methods including doing all the work on a new Thread (which won't work since all the UI needs to be on the main thread) as well as using AutoResetEvent to wait for the auth.Completed event to fire. 我尝试了几种不同的方法,包括在新线程上完成所有工作(由于所有UI都需要在主线程上,因此无法工作)以及使用AutoResetEvent等待auth.Completed事件触发。 But when using this it will usually block the thread and not even present our external webpage for the user. 但是当使用它时,它通常会阻塞线程,甚至不会为用户显示我们的外部网页。

Whats making this more complicated is trying to deal with all the different events without getting ahead of ourselves. 让这更复杂的是试图处理所有不同的事件而不是超越自己。 So first we need to wait for auth.Completed to fire, then from inside that event we need to wait for asynchronous request.GetResponseAsync().ContinueWith<Task<bool>>(async t => ... event to finish, and finally wait for the database to complete its task and somehow push the return value all the way back to the calling procedure. 所以首先我们需要等待auth.Completed触发,然后从该事件内部我们需要等待异步request.GetResponseAsync().ContinueWith<Task<bool>>(async t => ... event to finish,and终于等待数据库完成其任务,并以某种方式将返回值一直推回到调用过程。

As of right now, this code will perform all the desired functions, but will return false since we aren't waiting for any of the events to fire. 截至目前,此代码将执行所有所需的功能,但将返回false,因为我们不等待任何事件触发。

public bool LoginFacebook(bool isLinkedAccount)
    {
#if __IOS__

        UIKit.UIWindow window = UIKit.UIApplication.SharedApplication.KeyWindow;
        UIKit.UIViewController viewController = window.RootViewController;
#endif
        var auth = new OAuth2Authenticator(
                              clientId: "***********",
                              scope: "email",
                              authorizeUrl: new Uri("https://m.facebook.com/dialog/oauth/"),
                              redirectUrl: new Uri("http://www.facebook.com/connect/login_success.html")
                              );

#if __ANDROID__
        Forms.Context.StartActivity(auth.GetUI(Android.App.Application.Context));
#elif __IOS__
        if (viewController != null)
        {
            while (viewController.PresentedViewController != null)
                viewController = viewController.PresentedViewController;
            viewController.PresentViewController(auth.GetUI(), true, null);
        }
#endif
        // If authorization succeeds or is canceled, .Completed will be fired.
        auth.AllowCancel = true;
        auth.Completed += (sender, eventArgs) =>
        {
#if __IOS__
            viewController.DismissViewController(true, null);
#endif
            if (eventArgs.IsAuthenticated)
            {
                var request = new OAuth2Request("GET", new Uri("https://graph.facebook.com/me"), null, eventArgs.Account);
                request.GetResponseAsync().ContinueWith<Task<bool>>(async t =>
                {
                    if (t.IsFaulted)
                        Console.WriteLine("Error: " + t.Exception.InnerException.Message);
                    else
                    {
                        try
                        {
                            string content = t.Result.GetResponseText();
                            Console.WriteLine(content);
                            JObject user = JsonConvert.DeserializeObject<JObject>(content);
                            Facebook profile = new Facebook();
                            profile.uuid = user["id"].ToString();
                            profile.firstname = user["first_name"].ToString();
                            profile.lastname = user["last_name"].ToString();
                            profile.email = user["email"].ToString();
                            profile.url = user["link"].ToString();

                            //Get picture
                            profile.photo = "https://graph.facebook.com/" + profile.uuid + "/picture?type=large";

                            if (!isLinkedAccount)
                            {
                                //App.AnimateLoading(LoadingImage, OverlayImage, LoadingMessage);
                                return await DatabaseInstance.InsertSocialProfile(SocialNetworks.Facebook, profile, true);
                            }
                            else
                            {
                                await DatabaseInstance.LinkSocialAccountToProfile(App.UserProfile, profile, SocialNetworks.Facebook);
                                return true;
                            }
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.ToString());
                        }
                    }
                    return false;
                });
            }
        };
        return false;
    }

You can use TaskCompletionSource to represent an awaitable task, to negotiate between the event handler and the rest of an async method. 您可以使用TaskCompletionSource来表示一个等待的任务,在事件处理程序和其他async方法之间进行协商。

For brevity and clarity, I've trimmed all the #ifdef code from your example, focusing just on the core. 为了简洁和清晰,我从你的例子中删除了所有#ifdef代码,只关注核心。 With that in mind, here's what that would look like: 考虑到这一点,这是什么样子:

public async bool LoginFacebook(bool isLinkedAccount)
{
    var auth = new OAuth2Authenticator(
                          clientId: "***********",
                          scope: "email",
                          authorizeUrl: new Uri("https://m.facebook.com/dialog/oauth/"),
                          redirectUrl: new Uri("http://www.facebook.com/connect/login_success.html")
                          );

    // If authorization succeeds or is canceled, .Completed will be fired.
    auth.AllowCancel = true;

    TaskCompletionSource<bool> completionSource = new TaskCompletionSource<bool>();

    auth.Completed += (sender, eventArgs) =>
    {
        completionSource.SetResult(eventArgs.IsAuthenticated);
    };

    if (!(await completionSource.Task))
    {
        return false;
    }

    var request = new OAuth2Request("GET", new Uri("https://graph.facebook.com/me"), null, eventArgs.Account);
    var t = await request.GetResponseAsync();

    if (t.IsFaulted)
    {
        Console.WriteLine("Error: " + t.Exception.InnerException.Message);
        return false;
    }

    try
    {
        string content = t.Result.GetResponseText();
        Console.WriteLine(content);
        JObject user = JsonConvert.DeserializeObject<JObject>(content);
        Facebook profile = new Facebook();
        profile.uuid = user["id"].ToString();
        profile.firstname = user["first_name"].ToString();
        profile.lastname = user["last_name"].ToString();
        profile.email = user["email"].ToString();
        profile.url = user["link"].ToString();

        //Get picture
        profile.photo = "https://graph.facebook.com/" + profile.uuid + "/picture?type=large";

        if (!isLinkedAccount)
        {
            return await DatabaseInstance.InsertSocialProfile(SocialNetworks.Facebook, profile, true);
        }
        else
        {
            await DatabaseInstance.LinkSocialAccountToProfile(App.UserProfile, profile, SocialNetworks.Facebook);
            return true;
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
        return false;
    }
}

Naturally, with the change of this method to be an async method, the caller now can wait asynchronously on the result, avoiding the blocking of the main thread. 当然,通过将此方法更改为async方法,调用方现在可以在结果上异步等待,从而避免阻塞主线程。

Of course, lacking a good, minimal , complete code example to start with, the above is just browser-edited code. 当然,缺少一个好的, 最小的完整的代码示例 ,上面只是浏览器编辑的代码。 I did not compile it, never mind test it. 我没有编译它,没关系测试它。 But the above is the general idea. 但以上是一般的想法。 I assume you can incorporate the concept in your actual code. 我假设您可以将该概念合并到实际代码中。

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

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