简体   繁体   中英

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. 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. 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.

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.

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.

For brevity and clarity, I've trimmed all the #ifdef code from your example, focusing just on the core. 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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