如何将 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 需要传递 IFacebookCallback
和 GraphRequest.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;
}
我正在 Xamarin Forms 中实现本机 Android Facebook 身份验证,并且正在努力解决 Facebook SDK 破坏我的 await/async 链的问题。 SDK 需要传递 IFacebookCallback
和 GraphRequest.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 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;
}