2012-12-31 15 views
23

Ho un webservice scritto in Yii (framework php).Come posso usare async/attendi per chiamare un webservice?

Uso C# e Visual Studio 2012 per sviluppare un'applicazione WP8. Ho aggiunto un riferimento di servizio al mio progetto (Aggiungi riferimento al servizio). Quindi sono in grado di utilizzare le funzioni di 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); 

funzione getTestAsync e loginAsync ritorno void ed entrambi sono asincroni. È possibile che le funzioni restituiscano Task<T>? Vorrei utilizzare le parole chiave async/await nel mio programma.

Risposta:

Grazie per il vostro aiuto.

Il seguente codice sembra funzionare.

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

mi chiamano in questo modo

 client = new YChatWebService.WebServiceControllerPortTypeClient(); 
     var login = await client.LoginAsyncTask(this.username, this.password); 
+0

Basta modificare il tipo di restituzione vuoto in Attività, quindi è possibile chiamare attendere client.loginAsync (this.username, this.password); –

+1

@NickBray E quindi non funzionerà perché non si sta effettivamente restituendo un'attività completata al momento giusto ... – Servy

+0

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

risposta

28

Supponendo che loginAsync restituisce void, e gli incendi di evento loginCmpleted quando login è fatto, questo è chiamato il modello asincrono basato su eventi, o EAP.

Per convertire EAP in attesa/asincrona, consultare Tasks and the Event-based Asynchronous Pattern. In particolare, ti consigliamo di utilizzare TaskCompletionSource per convertire il modello basato su eventi in un modello basato su attività. Una volta ottenuto un modello basato su attività, è possibile utilizzare la funzione di attesa sexy del C# 5.

Ecco un esempio:

// 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(); 
    } 
} 

Ora che hai convertito il modello di programmazione asincrona basata su eventi a uno task-based, è ora possibile utilizzare attendono:

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

Grazie. Ho dovuto familiarizzare con termini come espressioni lambda, delegati ... "Quindi tutto è molto nuovo per me. Il compilatore ha un problema con" e => e "quindi l'ho cambiato in"() => e "e non c'è opzione TaskCreationOptions. DetachedFromParent. Cosa dovrei usare invece? – MPeli

+0

TaskCreateOptions.Non dovrebbe essere OK qui Sì, errore mio, dovrebbe essere() => e, o() => e.Foo, qualunque sia la proprietà che vuoi tirare fuori. aggiorneremo il codice –

+0

@MPeli Se la risposta è stata risolta per te, contrassegnala come risposta.Grazie –

7

nell'inserire la tua riferimento al servizio assicurarsi di aver selezionato Generate Task based operations in Advanced sezione. questo creerà metodi awaitable come LoginAsync ritorno Task<string>

+0

Questo sta usando un modello basato su eventi, quindi 'FromAsync' non sarà di aiuto. – Servy

+0

@Servy Ho usato l'url nella domanda e generato automaticamente le funzioni attendibili. Qualcosa di sbagliato? – I4V

+0

Non riesco a selezionare Genera operazioni basate su attività perché è disattivato. Sembra che sia disabilitato per i progetti WP8. Vedi [questo argomento] (http://stackoverflow.com/questions/13266079/wp8-sdk-import-service-reference-with-task-based-operations-not-possible) – MPeli

-3

Se vogliono essere in grado di attendere i metodi, dovrebbero restituire Task. Non puoi attendere un metodo che restituisce il nulla. Se si desidera che restituiscano un valore, come int che devono restituire Task<int>, il metodo deve restituire int.

public async Task loginAsync(string username, string password) {} 

Poi si può chiamare

Task t = loginAsync(username, password); 
//login executing 
//do something while waiting 

await t; //wait for login to complete 
+0

Chiede * come * per farlo. Non sa come un modello basato su eventi possa restituire un compito. – Servy

+0

Prendi lo stesso metodo che avevi e metti semplicemente Task al posto del vuoto. Quindi puoi chiamare attendi nel tuo programma. Non è necessario ulteriore lavoro. Come usarlo è una storia diversa. –

+0

Non è possibile modificare solo il tipo di ritorno del metodo ed essere fatto. La domanda chiede in particolare come modificare l'implementazione del metodo in modo che restituisca effettivamente un'attività. La domanda è chiedersi come si crea un 'Task' che viene completato quando i dati sono pronti e non si risponde a questo. Vedi la risposta di Giuda per come la realizzi effettivamente. – Servy

3

ho dovuto fare questo un paio di volte nel corso dell'ultimo anno e Ho usato sia @ codice di Giuda sopra e la original example egli ha fatto riferimento, ma ogni volta che ho riscontrato il seguente problema con entrambi: la chiamata asincrona funziona ma non completa. Se lo passo attraverso, posso vedere che entrerà nel metodo TransferCompletion ma lo e.UserState == tcs sarà sempre false.

Si scopre che i metodi asincroni del servizio Web come gli OP loginAsync dispongono di due firme.Il secondo accetta un parametro userState. La soluzione è passare l'oggetto TaskCompletionSource<T> creato come questo parametro. In questo modo e.UserState == tcs restituirà true.

Nell'OP, lo e.UserState == tcs è stato rimosso per far funzionare il codice che è comprensibile - anche io ero tentato. Ma credo che questo sia lì per garantire che l'evento corretto sia completato.

Il codice completo è:

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

In alternativa, credo che ci sia una proprietà tcs.Task.AsyncState troppo che fornirà il userState. Così si potrebbe fare qualcosa di simile:

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

Questo è quello che ho cercato inizialmente perché ci sembrava un approccio più leggero e ho potuto passare un Guid, piuttosto che l'oggetto TaskCompletionSource pieno. Stephen Cleary ha un good write-up of the AsyncState se sei interessato.

Problemi correlati