[英]How can I use async/await to call a webservice?
我使用 C# 和 Visual Studio 2012 来开发 WP8 应用程序。 我向我的项目添加了一个服务引用(添加服务引用)。 所以我能够使用网络服务功能。
client = new YChatWebService.WebServiceControllerPortTypeClient();
client.loginCompleted += client_loginCompleted; // this.token = e.Result;
client.loginAsync(this.username, this.password);
client.getTestCompleted += client_getTestCompleted;
client.getTestAsync(this.token);
函数getTestAsync
和loginAsync
返回void
并且两者都是异步的。 函数是否有可能返回Task<T>
? 我想在我的程序中使用async
/ await
关键字。
假设 loginAsync 返回 void,并且 loginCmpled 事件在登录完成时触发,这称为基于事件的异步模式或 EAP。
要将 EAP 转换为 await/async,请参阅任务和基于事件的异步模式。 特别是,您需要使用 TaskCompletionSource 将基于事件的模型转换为基于任务的模型。 一旦你有了一个基于任务的模型,你就可以使用 C# 5 的性感等待功能。
下面是一个例子:
// Use LoginCompletedEventArgs, or whatever type you need out of the .loginCompleted event
// This is an extension method, and needs to be placed in a static class.
public static Task<LoginCompletedEventArgs> LoginAsyncTask(this YChatWebService.WebServiceControllerPortTypeClient client, string userName, string password)
{
var tcs = CreateSource<LoginCompletedEventArgs>(null);
client.loginCompleted += (sender, e) => TransferCompletion(tcs, e, () => e, null);
client.loginAsync(userName, password);
return tcs.Task;
}
private static TaskCompletionSource<T> CreateSource<T>(object state)
{
return new TaskCompletionSource<T>(
state, TaskCreationOptions.None);
}
private static void TransferCompletion<T>(
TaskCompletionSource<T> tcs, AsyncCompletedEventArgs e,
Func<T> getResult, Action unregisterHandler)
{
if (e.UserState == tcs)
{
if (e.Cancelled) tcs.TrySetCanceled();
else if (e.Error != null) tcs.TrySetException(e.Error);
else tcs.TrySetResult(getResult());
if (unregisterHandler != null) unregisterHandler();
}
}
现在您已经将基于事件的异步编程模型转换为基于任务的异步编程模型,您现在可以使用 await:
var client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask("myUserName", "myPassword");
添加您的服务参考时,请确保您在Advanced
部分选择了Generate Task based operations
。 这将创建可等待的方法,例如LoginAsync
返回Task<string>
去年我不得不这样做几次,并且我使用了上面@Judah 的代码和他引用的原始示例,但每次我都遇到以下问题时:异步调用有效但没有完成。 如果我逐步完成它,我可以看到它将进入TransferCompletion
方法,但e.UserState == tcs
将始终为false
。
事实证明,像 OP 的loginAsync
这样的 Web 服务异步方法有两个签名。 第二个接受一个userState
参数。 解决方案是将您创建的TaskCompletionSource<T>
对象作为此参数传递。 这样e.UserState == tcs
将返回 true。
在 OP 中,删除了e.UserState == tcs
以使代码正常工作,这是可以理解的 - 我也受到了诱惑。 但我相信这是为了确保完成正确的事件。
完整代码是:
public static Task<LoginCompletedEventArgs> RaiseInvoiceAsync(this Client client, string userName, string password)
{
var tcs = CreateSource<LoginCompletedEventArgs>();
LoginCompletedEventHandler handler = null;
handler = (sender, e) => TransferCompletion(tcs, e, () => e, () => client.LoginCompleted -= handler);
client.LoginCompleted += handler;
try
{
client.LoginAsync(userName, password, tcs);
}
catch
{
client.LoginCompleted -= handler;
tcs.TrySetCanceled();
throw;
}
return tcs.Task;
}
或者,我相信也有一个tcs.Task.AsyncState
属性可以提供userState
。 所以你可以这样做:
if (e.UserState == taskCompletionSource || e.UserState == taskCompletionSource?.Task.AsyncState)
{
if (e.Cancelled) taskCompletionSource.TrySetCanceled();
else if (e.Error != null) taskCompletionSource.TrySetException(e.Error);
else taskCompletionSource.TrySetResult(getResult());
unregisterHandler();
}
这是我最初尝试的方法,因为它似乎是一种更轻松的方法,而且我可以传递 Guid 而不是完整的 TaskCompletionSource 对象。 如果您有兴趣,Stephen Cleary 有一篇关于 AsyncState的好文章。
(从 OP 复制,根据https://meta.stackexchange.com/a/150228/136378 )
回答:
以下代码似乎有效。
internal static class Extension
{
private static void TransferCompletion<T>(
TaskCompletionSource<T> tcs, System.ComponentModel.AsyncCompletedEventArgs e,
Func<T> getResult)
{
if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(getResult());
}
}
public static Task<loginCompletedEventArgs> LoginAsyncTask(
this YChatWebService.WebServiceControllerPortTypeClient client,
string userName, string password)
{
var tcs = new TaskCompletionSource<loginCompletedEventArgs>();
client.loginCompleted += (s, e) => TransferCompletion(tcs, e, () => e);
client.loginAsync(userName, password);
return tcs.Task;
}
}
我这样叫
client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask(this.username, this.password);
如果您希望能够等待方法,它们应该返回 Task。 您不能等待返回 void 的方法。 如果你希望它们返回一个值,比如 int 他们应该返回Task<int>
那么该方法应该返回 int。
public async Task loginAsync(string username, string password) {}
然后你可以打电话
Task t = loginAsync(username, password);
//login executing
//do something while waiting
await t; //wait for login to complete
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.