2009-09-27 3 views
4

Modifier: Déplacement de la question vers le haut.Comment est-ce que je peux éviter de passer/stocker un délégué en utilisant BeginInvoke et EndInvoke?

Mise à jour: Trouvé un exemple par Microsoft, niché sur un peu plus de code à la fin.

Mes questions sont les suivantes:

  1. Est-il sûr d'appeler plusieurs BeginInvoke fait appel à la même instance de délégué, ou dois-je construire une nouvelle instance de délégué pour chaque appel de méthode en vol?
  2. Si je dois construire de nouvelles instances pour chacun, existe-t-il un moyen d'obtenir le délégué original hors de la valeur IAsyncResult?
  3. Existe-t-il un autre moyen, mieux, d'ajouter un support asynchrone à ma classe que d'utiliser des délégués?

Plus d'informations ci-dessous.

Je suis en train d'ajouter un support asynchrone à une de mes classes, et j'ai pensé que je le ferais simplement.

Prenez cette classe:

public class Calculator 
{ 
    public Int32 Add(Int32 a, Int32 b) 
    { 
     return a + b; 
    } 
} 

Je pensais que je pourrais simplement faire ceci:

public class Calculator 
{ 
    private delegate Int32 AddDelegate(Int32 a, Int32 b); 
    public Int32 Add(Int32 a, Int32 b) 
    { 
     return a + b; 
    } 

    public IAsyncResult BeginAdd(Int32 a, Int32 b, 
     AsyncCallback callback, Object obj) 
    { 
     return new AddDelegate(Add).BeginInvoke(a, b, callback, obj); 
    } 

    public Int32 EndAdd(IAsyncResult ar) 
    { 
     return new AddDelegate(Add).EndInvoke(ar); 
    } 
} 

Cela ne fonctionne pas, comme les deux méthodes chacun construit son propre objet délégué, et. L'appel EndInvoke vérifie si l'instance de délégué sur laquelle je l'appelle est la même que celle que j'ai appelée BeginInvoke.

La façon la plus simple de gérer ce serait juste stocker une référence dans une variable, comme ceci:

public class Calculator 
{ 
    private delegate Int32 AddDelegate(Int32 a, Int32 b); 
    private AddDelegate _Add; 

    public Calculator() 
    { 
     _Add = new AddDelegate(Add); 
    } 

    public Int32 Add(Int32 a, Int32 b) 
    { 
     return a + b; 
    } 

    public IAsyncResult BeginAdd(Int32 a, Int32 b, 
     AsyncCallback callback, Object obj) 
    { 
     return _Add.BeginInvoke(a, b, callback, obj); 
    } 

    public Int32 EndAdd(IAsyncResult ar) 
    { 
     return _Add.EndInvoke(ar); 
    } 
} 

Notez que je suis pleinement conscient des problèmes permettant de multiples méthodes d'instance sur la même classe à exécuter en même temps, en ce qui concerne l'état partagé, etc.


Mise à jour: Je trouve cet exemple ici par Microsoft sur Asynchronous Delegates Programming Sample. Il montre le renvoi de la référence IAsyncResult à un objet AsyncResult, puis je peux obtenir l'instance de délégué d'origine via la propriété AsyncDelegate.

Est-ce une approche sécuritaire?

En d'autres termes, la classe suivante est-elle correcte?

public class Calculator 
{ 
    private delegate Int32 AddDelegate(Int32 a, Int32 b); 

    public Int32 Add(Int32 a, Int32 b) 
    { 
     return a + b; 
    } 

    public IAsyncResult BeginAdd(Int32 a, Int32 b, AsyncCallback callback, Object obj) 
    { 
     return new AddDelegate(Add).BeginInvoke(a, b, callback, obj); 
    } 

    public Int32 EndAdd(IAsyncResult ar) 
    { 
     AddDelegate del = (AddDelegate)((AsyncResult)ar).AsyncDelegate; 
     return del.EndInvoke(ar); 
    } 
} 

Répondre

4

Edit: si vous voulez dire que le délégué lui-même - je pense que vous pouvez simplement faire:

public Int32 EndAdd(IAsyncResult ar) 
{ 
    var d = (AddDelegate)((AsyncResult)ar).AsyncDelegate; 
    return d.EndInvoke(ar); 
} 

Vous pouvez toujours capturer dans le délégué; quelque chose comme ici: Async without the Pain, qui vous permet d'utiliser un rappel Action (ou Action<T>).

D'autres modèles communs impliquent des événements pour le rappel, et peut-être ThreadPool.QueueUserWorkItem; beaucoup plus simple que IAsyncResult.


Mettre tout cela ensemble; voici un exemple où ni l'appelant ni le code doit être stressé à propos IAsyncResult:

using System; 
using System.Runtime.Remoting.Messaging; 
public class Calculator 
{ 
    private delegate Int32 AddDelegate(Int32 a, Int32 b); 
    public Int32 Add(Int32 a, Int32 b) 
    { 
     return a + b; 
    } 

    public IAsyncResult BeginAdd(Int32 a, Int32 b, 
     AsyncCallback callback, Object obj) 
    { 
     return new AddDelegate(Add).BeginInvoke(a, b, callback, obj); 
    } 

    public Int32 EndAdd(IAsyncResult ar) 
    { 
     var d = (AddDelegate)((AsyncResult)ar).AsyncDelegate; 
     return d.EndInvoke(ar); 
    } 
} 
static class Program 
{ 
    static void Main() 
    { 
     Calculator calc = new Calculator(); 
     int x = 1, y = 2; 
     Async.Run<int>(
      (ac,o)=>calc.BeginAdd(x,y,ac,o), 
      calc.EndAdd, result => 
      { 
       Console.WriteLine(result()); 
      }); 
     Console.ReadLine(); 
    } 
} 
static class Async 
{ 
    public static void Run<T>(
    Func<AsyncCallback, object, IAsyncResult> begin, 
    Func<IAsyncResult, T> end, 
    Action<Func<T>> callback) 
    { 
     begin(ar => 
     { 
      T result; 
      try 
      { 
       result = end(ar); // ensure end called 
       callback(() => result); 
      } 
      catch (Exception ex) 
      { 
       callback(() => { throw ex; }); 
      } 
     }, null); 
    } 
} 
+0

J'ai besoin des valeurs retournées, donc IAsyncResult et le soutien des délégués a l'air tache sur mes besoins, mais je Regarde ton code. Notez que les deux méthodes RunAsync statiques que vous avez définies ici ont la même liste de paramètres, donc elles ne seront pas compilées. –

+0

Ok, j'imagine que j'ai trouvé ce casting via AsyncResult pendant que vous écriviez. Donc, c'est sûr de le faire? Il ne va pas y avoir une situation avec des délégués comme ceci où l'objet retourné n'est * pas * un objet AsyncResult? (notez que je ne pose pas de questions sur IAsyncResult dans le cas général, juste de delegate.BeginInvoke.) –

+0

Voir ma réponse pour une méthode qui ne dépend pas d'une implémentation spécifique de IAsyncResult – thecoop

2

je stocke toujours l'instance de délégué je l'ai appelé sur le cadre du AsyncState d'un BeginInvoke, de cette façon, vous pouvez toujours l'obtenir sans avoir à compter sur une mise en œuvre spécifique de IAsyncResult

une fois que vous avez votre IAsyncResult:

IAsyncResult ar; // this has your IAsyncResult from BeginInvoke in it 

MethodInvoker d = (MethodInvoker)ar.AsyncState; 
d.EndInvoke(ar); 
+0

Cela signifie que je vais perdre la capacité de paramètre supplémentaire de la fonction de rappel. Je peux sacrifier cette partie, juste besoin de peser les différentes solutions les unes contre les autres maintenant. –

+0

Vous pouvez stocker un objet [] dans AsyncState si vous souhaitez y stocker d'autres éléments – thecoop