web-dev-qa-db-fra.com

Comment puis-je utiliser async / wait pour appeler un service Web?

J'ai un webservice écrit en Yii (framework php).

J'utilise C # et Visual Studio 2012 pour développer une application WP8. J'ai ajouté une référence de service à mon projet (Ajouter une référence de service). Je peux donc utiliser les fonctions de 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); 

la fonction getTestAsync et loginAsync renvoie void et les deux sont asynchrones. Est-il possible que les fonctions retournent Task<T>? Je voudrais utiliser les mots clés async/await dans mon programme.

Réponse:

Merci de votre aide.

Le code suivant semble fonctionner.

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

Je l'appelle comme ça

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

En supposant que loginAsync renvoie vide et que l'événement loginCmpleted se déclenche lorsque la connexion est terminée, cela s'appelle le modèle asynchrone basé sur les événements, ou EAP.

Pour convertir EAP en attente/async, consultez Tâches et modèle asynchrone basé sur les événements . En particulier, vous souhaiterez utiliser la TaskCompletionSource pour convertir le modèle basé sur les événements en un modèle basé sur les tâches. Une fois que vous avez un modèle basé sur les tâches, vous pouvez utiliser la fonction d'attente sexy de C # 5.

Voici un exemple:

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

Maintenant que vous avez converti le modèle de programmation asynchrone basé sur les événements en un modèle basé sur les tâches, vous pouvez maintenant utiliser attendent:

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

Lors de l'ajout de votre référence de service, assurez-vous d'avoir sélectionné Generate Task based operations dans Advanced section. cela créera des méthodes attendues comme LoginAsync renvoyant Task<string>

7
I4V

J'ai dû le faire plusieurs fois au cours de la dernière année et j'ai utilisé à la fois le code de @ Judah ci-dessus et le exemple original qu'il a référencé mais à chaque fois que je rencontre le problème suivant avec les deux: l'appel asynchrone fonctionne mais ne se termine pas . Si je le parcoure, je peux voir qu'il entrera dans la méthode TransferCompletion mais le e.UserState == tcs sera toujours false.

Il s'avère que les méthodes asynchrones de service Web comme les OP loginAsync ont deux signatures. Le second accepte un paramètre userState. La solution est de passer le TaskCompletionSource<T> objet que vous avez créé comme paramètre. De cette façon, le e.UserState == tcs retournera vrai.

Dans l'OP, le e.UserState == tcs a été supprimé pour que le code fonctionne, ce qui est compréhensible - j'ai été tenté aussi. Mais je crois que c'est là pour garantir que l'événement correct est terminé.

Le code complet est:

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

Alternativement, je crois qu'il y a un tcs.Task.AsyncState propriété également qui fournira le userState. Vous pouvez donc faire quelque chose comme:

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

C'est ce que j'ai essayé au départ car cela semblait une approche plus légère et je pouvais passer un Guid plutôt que l'objet TaskCompletionSource complet. Stephen Cleary a un bonne rédaction de l'AsyncState si vous êtes intéressé.

5
Digbyswift