如何使用async / await来调用Web服务?

我有一个用Yii编写的web服务 (php框架)。

我使用C#和Visual Studio 2012开发WP8应用程序。 我在项目中添加了一个服务引用(添加服务引用)。 所以我能够使用webservicefunction。

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

function getTestAsyncloginAsync返回void ,两者都是异步的。 函数是否可以返回Task ? 我想在我的程序中使用async / await关键字。

回答:

谢谢您的帮助。

以下代码似乎有效。

  internal static class Extension { private static void TransferCompletion( TaskCompletionSource tcs, System.ComponentModel.AsyncCompletedEventArgs e, Func getResult) { if (e.Error != null) { tcs.TrySetException(e.Error); } else if (e.Cancelled) { tcs.TrySetCanceled(); } else { tcs.TrySetResult(getResult()); } } public static Task LoginAsyncTask(this YChatWebService.WebServiceControllerPortTypeClient client, string userName, string password) { var tcs = new TaskCompletionSource(); 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); 

假设loginAsync返回void,并且在登录完成时触发loginCmpleted事件,这称为基于事件的异步模式或EAP。

要将EAP转换为await / async,请参阅Tasks和基于事件的异步模式 。 特别是,您需要使用TaskCompletionSource将基于事件的模型转换为基于任务的模型。 一旦你有了一个基于任务的模型,你就可以使用C#5的性感等待function。

这是一个例子:

 // 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 LoginAsyncTask(this YChatWebService.WebServiceControllerPortTypeClient client, string userName, string password) { var tcs = CreateSource(null); client.loginCompleted += (sender, e) => TransferCompletion(tcs, e, () => e, null); client.loginAsync(userName, password); return tcs.Task; } private static TaskCompletionSource CreateSource(object state) { return new TaskCompletionSource( state, TaskCreationOptions.None); } private static void TransferCompletion( TaskCompletionSource tcs, AsyncCompletedEventArgs e, Func 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等待方法

在过去的一年中,我不得不这样做几次,我已经使用了上面的@Judah代码和他引用的原始示例,但每次我都遇到以下问题:异步调用有效但没有完成 。 如果我单步执行它,我可以看到它将进入TransferCompletion方法,但e.UserState == tcs将始终为false

事实certificate,像OP的loginAsync这样的Web服务异步方法有两个签名。 第二个接受userState参数。 解决方案是将您创建的TaskCompletionSource对象作为此参数传递。 这样e.UserState == tcs将返回true。

在OP中,删除了e.UserState == tcs以使代码工作,这是可以理解的 – 我也受到了诱惑。 但我相信这是为了确保正确的事件完成。

完整的代码是:

 public static Task RaiseInvoiceAsync(this Client client, string userName, string password) { var tcs = CreateSource(); 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有一个很好的写作 。

如果您希望能够等待方法,则应返回Task。 您无法等待返回void的方法。 如果你希望它们返回一个值,比如int,它们应该返回Task那么该方法应该返回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