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

时间:2012-12-31 15:21:26

标签: c# soap windows-phone-8 wsdl

我在webservice(php框架)中编写了Yii

我使用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); 

函数getTestAsyncloginAsync返回void,两者都是异步的。函数是否可以返回Task<T>?我想在我的计划中使用async / await个关键字。

答案:

感谢您的帮助。

以下代码似乎有用。

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

5 个答案:

答案 0 :(得分:32)

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

要将EAP转换为等待/异步,请咨询Tasks and the Event-based Asynchronous Pattern。特别是,您将希望利用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();
    } 
}

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

var client = new YChatWebService.WebServiceControllerPortTypeClient();
var login = await client.LoginAsyncTask("myUserName", "myPassword");

答案 1 :(得分:7)

添加服务参考时,请务必在Generate Task based operations部分选择Advanced。这将创建像LoginAsync返回Task<string>

这样的等待方法

答案 2 :(得分:5)

在过去的一年中我曾经多次这样做过,我已经使用了上面的@Judah的代码和他引用的original example,但每次我和以下两个问题都遇到了以下问题:异步调用有效但无法完成。如果我单步执行,我可以看到它会输入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有一个good write-up of the AsyncState

答案 3 :(得分:0)

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

答案 4 :(得分:-3)

如果您希望能够等待方法,则应返回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