2012-12-31 62 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