2012-06-23 5 views
2

Supposons, je l'interface IAsynchronous qui prend en charge deux méthodes pour faire une opération ("début/fin" pattern):Appels asynchrones composés - comment?

  • IAsyncResult BeginOperation(AsyncCallback callback, object state)
  • EndOperation(IAsyncResult ar)

Supposons aussi que je le classes A : IAsynchronous et B : IAsynchronous. Je dois implémenter la classe Compound : IAsynchronous. Le Compound « de fonctionnement de est d'invoquer A » opération s, attendre son achèvement, puis invoquer l » opération B et ensuite invoquer le rappel comme d'habitude.

La question est de savoir comment concevoir la classe Compound:

  • je garderais l'objet state original et invoquer le rappel d'origine lorsque l'opération complète du Compound. Ainsi, je ne peux pas simplement les passer à A.BeginOperation ou B.BeginOperation.
  • En outre, Compound.EndOperation devrait re-jeter une exception qui est jeté par A.EndOperation ou B.EndOperation. Si A.EndOperation a levé une exception, l'opération Compound ne doit pas appeler B.EndOperation car l'opération composée a déjà échoué.
  • Et s'il n'y a pas seulement 2 opérations internes, mais 3 ou plus? Y a-t-il un moyen commun?

Pour clarifier, considérons l'exemple suivant. Supposons, que nous avons la classe Multiplier qui prend en charge les méthodes suivantes:

  • IAsyncResult BeginMultiply(LargeNumber x, LargeNumber y, AsyncCallback callback, object state)
  • LargeNumber EndMultiply(IAsyncResult ar)

Et vous voulez écrire la classe ThreeMultiplier qui prend en charge les méthodes suivantes:

  • IAsyncResult BeginMultiply(LargeNumber x, LargeNumber y, LargeNumber z, AsyncCallback callback, object state)
  • LargeNumber EndMultiply(IAsyncResult ar)

La classe ThreeMultiplier doit utiliser la classe Multiplier pour calculer x * y * z. Pour ce faire, il calcule d'abord x * y (à travers Multiplier.Begin/EndMultiply), puis multiplie le résultat par z. Bien sûr, Multiplier.EndMultiply peut lancer SomeException qui échoue l'informatique de x * y * z à n'importe quelle étape.

Quel est le meilleur (ou bien) façon de mettre en œuvre ce? Y a-t-il un motif?

+0

Est-ce avec Visual Studio 2010? –

+0

@PeterRitchie De préférence .NET 3.5 – eigenein

Répondre

4

Je voudrais éviter APM (modèle de programmation asynchrone: utilisation de IAsyncResult et Begin * et * Fin) lors de l'écriture du nouveau code.

Dans Visual Studio 2010, la bibliothèque parallèle de tâches (TPL) a été introduit au cours de laquelle le modèle asynchrone Tâche (TAP) a été introduit.Ce modèle constitue la base des API de structure sous-jacentes prenant en charge les nouveaux mots clés async/await dans VS 2012 (C# 5). Vous pouvez envelopper les implémentations APM avec Task.FromAsync(); mais si vous écrivez un nouveau code, utiliser Tâche/Tâche serait un meilleur choix pour l'avenir. Avec TAP, vous enveloppez un délégué avec un objet Tâche qui exécutera le délégué de manière asynchrone.

Vous pouvez ensuite "continuer" avec d'autres tâches asynchrones qui s'exécuteront à la fin du premier délégué. Par exemple, si vous aviez deux délégués où l'on a besoin pour fonctionner à la fin de l'autre, vous pouvez le faire:

 Task.Factory.StartNew(() => MyMethod()) 
      .ContinueWith(() => MyOtherMethod()); 

Vous pouvez envelopper que dans une seule méthode:

public void AsyncCompound(Action firstAcction, Action secondAction) 
{ 
      Task.Factory.StartNew(firstAction) 
       .ContinueWith(secondAction); 
} 

.. Beaucoup moins de travail que la définition des classes IAsyncResult et l'implémentation d'une méthode Begin et End.

Comme pour .NET 3.5 - le modèle dominant est APM - il n'y a pas de classe "Task" disponible de manière cohérente. Il y a quelques implémentations de TPL qui pourraient fonctionner, mais je ne les ai pas utilisées. Alternativement, vous pouvez regarder dans Reactive Extensions car c'est une autre façon d'implémenter des opérations asynchrones - bien que basé sur des événements. APM peut être très rapide, donc, je vous recommande d'utiliser des délégués où vous le pouvez. Je recommande également réutilisant un implemention IAsyncResult comme celui à http://msdn.microsoft.com/en-us/magazine/cc163467.aspx Par exemple:

public class Multiplier 
{ 
    public LargeNumber Multiply(LargeNumber x, LargeNumber y) 
    { 
     return x * y; 
    } 

    public IAsyncResult BeginMultiply(LargeNumber x, LargeNumber y, AsyncCallback callback, object state) 
    { 
     AsyncResult<LargeNumber> ar = new AsyncResult<BigInteger>(callback, state); 
     ThreadPool.QueueUserWorkItem(o => 
     { 
      var asyncResult = (AsyncResult<LargeNumber>)o; 
      try 
      { 
       var largeNumber = Multiply(x, y); 
       asyncResult.SetAsCompleted(largeNumber, false); 
      } 
      catch (Exception e) 
      { 
       asyncResult.SetAsCompleted(e, false); 
      } 
     }, ar); 
     return ar; 
    } 

    public LargeNumber EndMultiply(IAsyncResult asyncResult) 
    { 
     var ar = (AsyncResult<LargeNumber>)asyncResult; 

     return ar.EndInvoke(); 
    } 

    public IAsyncResult BeginMultiply(LargeNumber x, LargeNumber y, LargeNumber z, AsyncCallback callback, object state) 
    { 
     AsyncResult<LargeNumber> ar = new AsyncResult<LargeNumber>(callback, state); 

     BeginMultiply(x, y, (asyncResult1) => 
     { 
      var firstResult = EndMultiply(asyncResult1); 
      BeginMultiply(firstResult, z, (asyncResult2) => 
      { 
       var secondResult = EndMultiply(asyncResult2); 
       ar.SetAsCompleted(secondResult, true); 
      }, state); 
     }, state); 
     return ar; 
    } 
} 

qui pourrait ensuite être utilisé comme suit pour calculer une valeur et de manière asynchrone revenir au thread courant:

var asyncResult = multiplier.BeginMultiply(x, y, z, ar => { }, null); 
var result = multiplier.EndMultiply(asyncResult); 

Ou, vous pouvez la chaîne vers d'autres code à exécuter sur un fil de fond:

 multiplier.BeginMultiply(x, y, z, ar => 
              { 
              var result = multiplier.EndMultiply(ar); 
              /* TODO: something with result on this background thread */ 
              }, null); 

... ce serait à vous de décider comment vous ge t ce résultat où il doit aller et comment cela interagit avec le fil qui a appelé BeginMultiply, voire pas du tout.

Questions connexes