如何将 async/await 与期望基于回调的异步 activity 的 SDK 一起使用

How to use async/await with an SDK that expects call-back based asynchronous activity

我正在 Xamarin Forms 中实现本机 Android Facebook 身份验证,并且正在努力解决 Facebook SDK 破坏我的 await/async 链的问题。 SDK 需要传递 IFacebookCallbackGraphRequest.IGraphJSONObjectCallback 的实现,它们定义回调以处理结果。我想在请求登录的代码中执行以下操作:

public async Task LogInFacebookAsync()
{
    var loginResult = await _facebookManager.Login();  
    // do stuff with result
}

但是,这就是我目前正在做的事情。
请求登录的代码:

public void LogInFacebookAsync()
{
    _facebookManager.Login(onFacebookLoginComplete);
}
private async Task<string> onFacebookLoginComplete(FacebookUser fbUser, string errorMessage) 
{
    // do stuff with result
}

这是我的 FacebookManager class,它实现了必要的接口。请注意,结果可能来自多个回调方法:OnCancel()OnError()OnCompleted(),每个回调方法都会调用上面代码传入的 _onLoginComplete。

public class FacebookManager : Java.Lang.Object, IFacebookManager, IFacebookCallback, GraphRequest.IGraphJSONObjectCallback
{
    public Func<FacebookUser, string, Task> _onLoginComplete;
    public ICallbackManager _callbackManager;
    private Activity _context;

    public FacebookManager()
    {
        this._context = CrossCurrentActivity.Current.Activity;
        _callbackManager = CallbackManagerFactory.Create();
        LoginManager.Instance.RegisterCallback(_callbackManager, this);
    }

    public async Task Login(Func<FacebookUser, string, Task> onLoginComplete)
    {
        _onLoginComplete = onLoginComplete;
        LoginManager.Instance.SetLoginBehavior(LoginBehavior.NativeWithFallback);
        LoginManager.Instance.LogInWithReadPermissions(_context, new List<string> { "public_profile", "email" });
        await Task.CompletedTask;
    }

    #region IFacebookCallback   
    public void OnCancel()
    {
        _onLoginComplete?.Invoke(null, "Canceled!");
    }
    public void OnError(FacebookException error)
    {
        _onLoginComplete?.Invoke(null, error.Message);
    }       
    public void OnSuccess(Java.Lang.Object result)
    {
        var n = result as LoginResult;
        if (n != null)
        {
            var request = GraphRequest.NewMeRequest(n.AccessToken, this);
            var bundle = new Android.OS.Bundle();
            bundle.PutString("fields", "id, first_name, email, last_name, picture.width(500).height(500)");
            request.Parameters = bundle;
            request.ExecuteAsync();
        }
    }
    #endregion

    #region GraphRequest.IGraphJSONObjectCallback
    public void OnCompleted(JSONObject p0, GraphResponse p1)
    {       
        // extract user
        _onLoginComplete?.Invoke(fbUser, string.Empty);   
    }
    #endregion
}

有什么方法可以包装这些操作,使我成为 "async all the way down"?

更新:

感谢 Paulo 。我已经在按照这些思路思考,而您确实明确了解决方案。我唯一关心的答案是它是线程安全的。如果双击一个按钮或同时进行两个需要 Facebook 登录的 API 调用怎么办?以下是我尝试使 LoginAsync() 方法更安全的尝试。但是,我不是线程安全方面的专家,我欢迎所有批评。

private TaskCompletionSource<FacebookUser> _loginTcs;
public async Task<FacebookUser> LoginAsync()
{
    Task<FacebookUser> loginTask = _loginTcs?.Task;
    // Don't want to create a new login request to the Facebook API if one is already being made.
    if (loginTask != null && (loginTask.Status == TaskStatus.Running || loginTask.Status == TaskStatus.WaitingForActivation 
        || loginTask.Status == TaskStatus.WaitingToRun || loginTask.Status == TaskStatus.WaitingForChildrenToComplete))
    {
        return await loginTask.ConfigureAwait(false);
    }

    this._loginTcs = new TaskCompletionSource<FacebookUser>();
    LoginManager.Instance.SetLoginBehavior(LoginBehavior.NativeWithFallback);
    LoginManager.Instance.LogInWithReadPermissions(_context, new List<string> { "public_profile", "email" });
    var user = await this._loginTcs.Task.ConfigureAwait(false);
    return user;
}

使用 TaskCompletionSource。不需要其他任何东西。

我对此知之甚少API(我很惊讶 Xamarin 没有提供它的惯用 .NET 版本)但是,大致来说,您需要做的是:

将任务完成源添加到您的 class 而不是 _onLoginComplete 委托:

// public Func<FacebookUser, string, Task> _onLoginComplete;
private TaskCompletionSource<FacebookUser> completion;

更改您的 OnCancel 取消任务的方法:

public void OnCancel()
{
    this.completion.TrySetCanceled();
}

更改您的 OnError 方法以完成错误的任务:

public void OnError(FacebookException error)
{
    this.completion.TrySetException(error);
}

更改您的OnCompleted以设置任务结果:

public void OnCompleted(JSONObject p0, GraphResponse p1)
{       
    // extract user
    this.completion.TrySetResult(fbUser);   
}

更改登录方法以创建任务完成源和return任务完成源的任务:

public async Task<FacebookUser> LoginAsync()
{
    this.completion = new TaskCompletionSource<FacebookUser>();
    LoginManager.Instance.SetLoginBehavior(LoginBehavior.NativeWithFallback);
    LoginManager.Instance.LogInWithReadPermissions(_context, new List<string> { "public_profile", "email" });
    await this.completion.Task;
}