【问题标题】:How to wait for a return value from an event handler without blocking main UI thread如何在不阻塞主 UI 线程的情况下等待来自事件处理程序的返回值
【发布时间】:2015-06-13 20:39:21
【问题描述】:

所以我有这个方法,它使用一个库来生成一个外部网页,以允许用户输入他们的 Facebook 凭据进行验证。输入后,我们将根据该用户的帐户创建配置文件并将其保存到数据库中。一旦我们将它保存到数据库中,我们期望一个布尔值来表示该帐户是否是唯一的。我们需要一些方法来等待数据库完成所有工作,然后再继续执行。

我尝试了几种不同的方法,包括在一个新线程上完成所有工作(因为所有 UI 都需要在主线程上,所以这不起作用)以及使用 AutoResetEvent 等待 @987654322 @要触发的事件。但是当使用它时,它通常会阻塞线程,甚至不会向用户展示我们的外部网页。

让这件事变得更复杂的是试图处理所有不同的事件而不是超越自己。因此,首先我们需要等待auth.Completed 触发,然后在该事件内部我们需要等待异步request.GetResponseAsync().ContinueWith<Task<bool>>(async t => ... 事件完成,最后等待数据库完成其任务并以某种方式将返回值一直推送回到调用过程。

到目前为止,此代码将执行所有所需的功能,但将返回 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;
    }

【问题讨论】:

    标签: c# multithreading asynchronous xamarin event-handling


    【解决方案1】:

    您可以使用TaskCompletionSource 表示一个等待任务,在事件处理程序和async 方法的其余部分之间进行协商。

    为了简洁明了,我已经从您的示例中删除了所有 #ifdef 代码,只关注核心。考虑到这一点,这就是它的样子:

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

    当然,随着这个方法变成async方法,调用者现在可以异步等待结果,避免了主线程的阻塞。

    当然,缺少a good, minimal, complete code example,上面只是浏览器编辑的代码。我没有编译它,别介意测试它。但以上是总体思路。我假设你可以在你的实际代码中加入这个概念。

    【讨论】:

    • 其中一个问题是,如果没有来自auth.Completed 事件的eventArgs,我将无法使用OAuth2Request
    • 啊,没注意。在这种情况下,只需将TaskCompletionSource&lt;T&gt; 中的T 设为eventArgs 值的适当类型,并将整个eventArgs 存储为任务结果,而不仅仅是IsAuthenticated 属性值(即completionSource.SetResult(eventArgs))。然后,在await 完成后,您将可以访问eventArgs 对象中的任何属性值(当然,您会将结果存储在局部变量中)。我会更新答案本身,但问题中没有足够的上下文让我知道在这种情况下T 应该是什么。
    猜你喜欢
    • 2015-09-18
    • 1970-01-01
    • 2019-12-02
    • 1970-01-01
    • 2011-06-17
    • 1970-01-01
    • 1970-01-01
    • 2020-12-01
    • 1970-01-01
    相关资源
    最近更新 更多