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

MPe*_*eli 27 c# soap wsdl windows-phone-8

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

我使用C#和Visual Studio 2012来开发WP8应用程序.我为我的项目添加了一个服务引用(添加服务引用).所以我可以使用webservice功能.

   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); 
Run Code Online (Sandbox Code Playgroud)

函数getTestAsyncloginAsync返回void都是异步的.函数是否可以返回Task<T>?我想在我的程序中使用async/ awaitkeywords.

回答:

谢谢您的帮助.

以下代码似乎有效.

   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); 
Run Code Online (Sandbox Code Playgroud)

我这样称呼它

   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); 
Run Code Online (Sandbox Code Playgroud)

Jud*_*ngo 33

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

要将EAP转换为await/async,请参阅Tasks和基于事件的异步模式.特别是,您将希望利用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();
    } 
}
Run Code Online (Sandbox Code Playgroud)

现在您已经将基于事件的异步编程模型转换为基于任务的异步编程模型,现在可以使用await:

var client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask("myUserName", "myPassword");
Run Code Online (Sandbox Code Playgroud)

  • 此代码将无效,因为用户状态为null。在TransferCompletion(`e.UserState == tcs`)中的检查将始终为false。不确定最好的方法是什么,但是如果将tcs作为第三个参数传递给`loginAsync`,它似乎可以正常工作。 (2认同)

I4V*_*I4V 7

添加服务参考时,请确保Generate Task based operationsAdvanced部分中选择.这将创造像LoginAsync返回一样的等待方法Task<string>


Dig*_*ift 6

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

事实证明,像OP这样的Web服务异步方法loginAsync有两个签名.第二个接受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;
}
Run Code Online (Sandbox Code Playgroud)

或者,我相信也有一个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();
}
Run Code Online (Sandbox Code Playgroud)

这是我最初尝试的,因为它看起来更轻松,我可以传递Guid而不是完整的TaskCompletionSource对象.如果您有兴趣,Stephen Cleary 会对AsyncState有一个很好的写作.