2012-12-31 57 views
23

我有一个webservice写在Yii(PHP框架)。如何使用异步/等待来调用Web服务?

我使用C#和Visual Studio 2012开发WP8应用。我添加了一个服务引用到我的项目(添加服务引用)。所以我能够使用web服务功能。

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

只需将void返回类型更改为Task,然后您可以调用await client.loginAsync(this.username,this.password); –

+1

@NickBray然后它不会工作,因为你不会实际返回一个在适当的时间完成的任务... – Servy

+0

http://msdn.microsoft.com/en-us/library/hh524395.aspx –

回答

28

假设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"); 
+0

谢谢。我必须熟悉术语,如lambda表达式,委托......“因此,对我来说,一切都是非常新的。编译器遇到了”e => e“的问题,所以我将它改为”()=> e“,并且没有任何选项TaskCreationOptions。 DetachedFromParent。我应该用什么来代替? – MPeli

+0

TaskCreateOptions.None应该可以在这里。是的,我的错误,它应该是()=> e,或()=> e.Foo,无论你想要从哪里拉下属性。 –

+0

@MPeli如果此答案为您解决,请将其标记为答案。谢谢。 –

7

虽然增加您的服务引用请确保您在Advanced部分选择Generate Task based operations。这将创建诸如LoginAsync返回的等待方法Task<string>

+0

这是使用基于事件的模型,所以'FromAsync'不会有帮助。 – Servy

+0

@Servy我在问题中使用了url,并自动生成了awaitable函数。哪里不对了? – I4V

+0

我无法选择生成基于任务的操作,因为它显示为灰色。它似乎是WP8项目禁用的。请参阅[本主题](http://stackoverflow.com/questions/13266079/wp8-sdk-import-service-reference-with-task-based-operations-not-possible) – MPeli

-3

如果您希望能够等待方法,他们应该返回任务。你不能等待一个返回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 
+0

他问*如何做到这一点。他不知道基于事件的模型如何返回任务。 – Servy

+0

采用与您相同的方法,只需将Task而不是void即可。然后你可以在你的程序中调用await。无需额外的工作。如何使用它是一个不同的故事。 –

+0

你不能只改变方法的返回类型并完成。问题是具体询问如何更改该方法的实现,以便它实际返回一个任务。问题是询问你如何创建一个在数据准备好时完成的“任务”,而你不回答这个问题。请参阅Judah的回答,告诉你如何实际执行它。 – Servy

3

我不得不这样做几次在过去一年和我用两个以上@犹大的代码和original example他引用但每次我遇到以下两个问题时:异步调用都能正常工作,但未完成。如果我通过它,我可以看到它将进入TransferCompletion方法,但e.UserState == tcs将始终为false

事实证明,Web服务异步方法,如OP的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; 
} 

另外,我相信这是一个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