2

J'utilise une API réseau qui fonctionne avec les rappels. Donc, fondamentalement, j'ai un tas appels de méthode que je dois utiliser dans cette bibliothèque 3ème partie qui ressemble à ceci:Comment faire pour encapsuler le rappel asynchrone avec Observable ou async/await?

void SendNetworkRequest(string requestType, Action<Response> callback) 

Je trouve le code d'obtenir un peu loufoque parce que toutes mes méthodes qui dépendent du réseau Les ressources de cette API tierce doivent également implémenter des rappels eux-mêmes. Par exemple, dans ma scène principale je veux obtenir les informations des joueurs et mon code ressemblerait à quelque chose comme ceci:

void InitMainScene() 
{ 
     _networkHelper.SendNetworkRequest("GetPlayersInfo",OnPlayerInfoResponse); 
} 

void OnPlayerInfoResponse(Response response) 
{ 
    _playerInfo = response.Info; 
} 

J'ai récemment obtenu en RX et je l'utilise abundently dans mon code. J'ai lu en lire un peu sur async/await. J'ai beaucoup expérimenté, surtout avec RX, et j'ai essayé de travailler avec Observable.FromAsync() mais je n'ai pas pu le faire fonctionner.

Qu'est-ce qui me manque ici? Comment est-ce que je peux écrire le code qui est plus propre et n'exige pas le rappel utilisant le RX ou l'async/l'attente? Ce qui suit est le psuedocode que je suis à la recherche:

 void InitMainScene() 
     { 
      _playerInfo = Overservable.DoMagicThing<Response>(() => { 
_networkHelper.SendNetworkRequest("GetPlayersInfo",(r) =>{return r;}); }); 
     } 

Répondre

1

Je ne suis pas sûr de RX. Cependant, vous pouvez convertir SendNetworkRequest à base de rappel à une tâche awaitable comme ceci:

void SendNetworkRequest(string requestType, Action<Response> callback) 
     { 
      // This code by third party, this is here just for testing 
      if (callback != null) 
      { 
       var result = new Response(); 
       callback(result); 
      } 
     } 

     Task<Response> SendNetworkRequestAsync(string requestType) 
     { 
      return Task.Run(() => 
      { 
       var t = new TaskCompletionSource<Response>(); 

       SendNetworkRequest(requestType, s => t.TrySetResult(s)); 

       return t.Task; 
      }); 
     } 

Maintenant, vous pouvez consommer SendNetworkRequestAsync avec async/attendre plus naturellement

+0

Merci beaucoup! c'est bien! Je vais jouer avec ça aujourd'hui. Question pour vous - recommandez-vous cette sémantique sur l'Overservable dans mon cas?BTW - Je n'ai pas encore utilisé async/await, et en fait, je le fais dans Unity3d qui ne supporte que .net 4.6 comme fonctionnalité expérimentale. – user3010678

+0

RX est idéal si vous gérez un flux d'événements. Dans votre cas, vous appelez SendNetworkRequest puis attendez que la tâche soit terminée, c'est tout. Donc oui, async/await est plus facile et plus raisonnable que Rx dans ce cas. Propre et facile –

1

Voici comment faire avec Rx. Je troqué Response pour string juste pour pouvoir compiler et tester, mais trivial d'échanger arrière .:

public IObservable<string> SendNetworkRequestObservable(string requestType) 
{ 
    return Observable.Create<string>(observer => 
    { 
     SendNetworkRequest(requestType, s => observer.OnNext(s)); 
     observer.OnCompleted(); 
     return Disposable.Empty; 
    }); 
} 

// Define other methods and classes here 
public void SendNetworkRequest(string requestType, Action<string> callback) 
{ 
    callback(requestType); // implementation for testing purposes 
} 
+0

Urrgh - Si vous renvoyez "Disposable.Empty", alors vous faites probablement quelque chose de mal. – Enigmativity

+0

merci @Schlomo! Cela m'aide beaucoup! Je vais jouer avec cela et essayer de mieux comprendre les implications de Disposable.Empty que l'énigmativité décrit – user3010678

+0

@Enigmativity, n'hésitez pas à développer pourquoi. Je ne suis pas d'accord: dans ce cas, il n'y a rien à éliminer, mais un abonnement nécessite un jetable. Ceci est un exemple parfait pour la raison pour laquelle 'Disposable.Empty' existe. – Shlomo

1

Voici mon avis sur le faire avec Rx.

Dans la classe NetworkHelper ajouter cette méthode:

public IObservable<Response> SendNetworkRequestObservable(string requestType) 
{ 
    return 
     Observable 
      .Create<Response>(observer => 
      { 
       Action<Response> callback = null; 
       var callbackQuery = 
        Observable 
         .FromEvent<Response>(h => callback += h, h => callback -= h) 
         .Take(1); 
       var subscription = callbackQuery.Subscribe(observer); 
       this.SendNetworkRequest(requestType, callback); 
       return subscription; 
      }); 
} 

Cela crée une observable qui utilise en interne Observable.FromEvent(...).Take(1) pour créer une observable sur la base attend un seul appel à la Action<Response> callback transmis à la méthode SendNetworkRequest.

Le seul problème avec ceci est que l'appel se produit sur le thread en cours jusqu'à la fin. Si vous voulez que ce code à exécuter sur un thread d'arrière-plan, mais renvoie le résultat au thread courant, vous pouvez le faire:

public IObservable<Response> SendNetworkRequestObservable(string requestType) 
{ 
    return 
     Observable 
      .Start(() => 
       Observable 
        .Create<Response>(observer => 
        { 
         Action<Response> callback = null; 
         var callbackQuery = 
          Observable 
           .FromEvent<Response>(h => callback += h, h => callback -= h) 
           .Take(1); 
         var subscription = callbackQuery.Subscribe(observer); 
         this.SendNetworkRequest(requestType, callback); 
         return subscription; 
        })) 
      .Merge(); 
} 

Quoi qu'il en soit vous appelleriez comme ceci:

var _playerInfo = 
    _networkHelper 
     .SendNetworkRequestObservable("GetPlayersInfo") 
     .Wait(); 

Juste une note: votre DoMagicThing est un appel synchrone. En Rx c'est un appel .Wait(), mais il est préférable d'utiliser un .Subscribe normal quand vous le pouvez.


Selon le commentaire, vous pouvez le faire avec async:

async void InitMainScene() 
{ 
    _playerInfo = await _networkHelper.SendNetworkRequestObservable("GetPlayersInfo"); 
} 
+0

Merci beaucoup pour cet exemple. J'ai passé un certain temps à essayer de le faire fonctionner dans mon jeu. Il semble cependant que la méthode .Wait() provoque le blocage d'Unity puis son crash. J'ai besoin de creuser pour savoir pourquoi - même async/wait example ci-dessous fait la même chose. Merci beaucoup pour l'aide! Je voulais vous demander - pensez-vous que async/await est une meilleure solution à ce problème, ou pensez-vous que votre solution est plus propre dans ce cas précis? Il est difficile de savoir lequel utiliser dans quelle situation je suis si inexpérimenté avec les deux maintenant. Merci encore! – user3010678

+0

@ user3010678 - Ce que vous ne devriez pas faire est d'utiliser '.Wait()'. Vous devriez utiliser un appel régulier .Subscribe (...) 'ou, mieux encore, utiliser' async' et faire ressembler l'appel: 'var _playerInfo = await _networkHelper.SendNetworkRequestObservable (" GetPlayersInfo ");'. Cela fonctionne avec les observables et renvoie la dernière valeur produite (s'il y en a plusieurs). – Enigmativity