2009-01-02 4 views
4

La réponse d'Andreas Huber à this question m'a donné une idée pour implémenter Concurrent<T> avec des délégués asynchrones au lieu du ThreadPool. Cependant, je trouve qu'il est plus difficile de comprendre ce qui se passe lorsqu'un AsyncCallback est passé à BeginInvoke, en particulier lorsque plusieurs threads ont accès à IAsyncResult. Malheureusement, ce cas ne semble pas être couvert à MSDN ou n'importe où je pourrais trouver. De plus, tous les articles que j'ai pu trouver étaient écrits avant les fermetures et les génériques étaient disponibles ou semblaient juste comme ça. Il y a plusieurs questions (et les réponses que j'espère sont vraies, mais je suis prêt à être déçu):Utilisation de BeginInvoke/EndInvoke dans un mode multithread. Comment AsyncCallback, AsyncWaitHandle et IsCompleted interagissent-ils?

1) L'utilisation d'une fermeture comme AsyncCallback ferait-elle une différence?
(pas un peu de chance)
2) Si un thread attend sur le AsyncWaitHandle, qu'il sera signalaient
a) avant que le rappel commence ou b) après la fin?
(Espérons que b)
3) Pendant que le rappel est en cours, que retournera IsCompleted? Possibilités que je peux voir:
a) true; b) false; c) false avant que le rappel appelle EndInvoke, true après.
(Espérons que b ou c)
4) Est-ce que DisposedObjectException sera lancé si un thread attend le AsyncWaitHandle après EndInvoke?
(J'espère que non, mais je m'attends à oui).

à condition que les réponses sont comme je l'espère, cela semble que cela devrait fonctionner:

public class Concurrent<T> { 
    private IAsyncResult _asyncResult; 
    private T _result; 

    public Concurrent(Func<T> f) { // Assume f doesn't throw exceptions 
     _asyncResult = f.BeginInvoke(
          asyncResult => { 
           // Assume assignment of T is atomic 
           _result = f.EndInvoke(asyncResult); 
          }, null); 
    } 

    public T Result { 
     get { 
      if (!_asyncResult.IsCompleted) 
       // Is there a race condition here? 
       _asyncResult.AsyncWaitHandle.WaitOne(); 
      return _result; // Assume reading of T is atomic 
     } 
    ... 

Si les réponses aux questions 1-3 sont ceux que j'espérer, il devrait y avoir aucune condition de raace ici, d'aussi loin que je puisse voir.

Répondre

2

Question 1

Je pense qu'une partie du problème est fausse idée. IAsyncResult n'est pas accessible à partir de plusieurs threads sauf si vous le transmettez explicitement à un. Si vous regardez l'implémentation des API de style Begin Begin dans la BCL, vous remarquerez que IAsyncResult n'est créé et détruit que depuis le thread où l'appel Begin *** ou End *** se produit réellement.

Question 2

AsyncWaitHandle doit être signalée après l'opération est terminée à 100%.

Question 3

IsCompleted doit retourner true une fois l'opération sous-jacente est terminée (pas plus de travail à faire).La meilleure façon de voir IsComplete est que si la valeur est

  1. vrai -> Calling End *** retourne immédiatement
  2. faux -> Callind End *** bloquera pendant une certaine période de temps

question 4

Cela dépend la mise en œuvre. Il n'y a aucun moyen de donner une réponse générale ici.

échantillons

Si vous êtes intéressé par une API qui vous permet d'exécuter facilement un délégué sur un autre thread et accéder au résultat lorsque vous avez terminé, consultez mon RantPack Utility Library. Il est disponible sous forme source et binaire. Il a une API Future entièrement étoffée qui permet le fonctionnement simultané des délégués.

De plus, il existe une implémentation de IAsyncResult qui couvre la plupart des questions de cet article.

+0

Alors que vos réponses seraient très applicables si je voulais implémenter IAsyncResult ici, je ne le fais pas. Je pose la question sur le comportement de System.Runtime.Remoting.Messaging.AsyncResult, celui effectivement retourné par BeginInvoke. –

+0

Question 1: C'est précisément ce que je voulais dire par "Malheureusement, ce cas ne semble pas être couvert à MSDN ou n'importe où je pourrais trouver." Je n'expose pas _asyncResult directement, mais seulement en attendant dans la propriété Result; mais les implémentations dans le cadre ne le font même pas. –

+0

Question 2: Bon, c'est ce que j'espérais. –

2

Je me suis intéressé récemment aux appels asynchrones. J'ai trouvé un pointeur vers un article avec un example implementation of an IAsyncResult par l'auteur respecté Jeffrey Richter. J'ai beaucoup appris sur le fonctionnement des appels asynchrones en étudiant cette implémentation.

Vous pourriez également voir si vous pouvez télécharger et examiner le code source pour le System.Runtime.Remoting.Messaging.AsyncResult vous êtes spécifiquement concerné par. Voici un link to instructions on how to do this in Visual Studio.

Pour ajouter un peu aux bonnes réponses JaredPar ...

1: Je crois que si vous définissez une fermeture qui peut être affecté à une variable de type AsyncCallback (prend un IAsyncResult et retourne vide), il doit travailler vous vous attendez à ce qu'une fermeture fonctionne comme ce délégué, mais je ne suis pas sûr qu'il pourrait y avoir des problèmes de portée. La portée locale d'origine devrait être retournée longtemps avant que le rappel ne soit invoqué (ce qui en fait une opération asynchrone), gardez cela à l'esprit en ce qui concerne les références aux variables locales (pile) et comment cela se comportera. Référencer les variables membres devrait être bien, je pense. 2: Je pense à partir de votre commentaire que vous avez peut-être mal compris la réponse à celui-ci. Dans l'exemple d'implémentation de Jeffrey Richter, le handle d'attente est signalé avant que le rappel ne soit appelé. Si vous y réfléchissez, ça doit être comme ça. Une fois qu'il appelle le rappel, il perd le contrôle de l'exécution. Supposons que la méthode de rappel lève une exception .... l'exécution pourrait se dérouler en arrière de la méthode qui a appelé le rappel et l'empêcher ainsi de signaler plus tard la poignée d'attente! La poignée d'attente doit donc être signalée avant que le rappel ne soit appelé. Ils sont également beaucoup plus proches dans le temps s'ils sont effectués dans cet ordre que s'ils ne signalent la poignée d'attente qu'après le retour du rappel.

3: Comme le dit JaredPar, IsCompleted devrait être de retour avant true le rappel et avant que la poignée d'attente est signalée. Cela est logique parce que si IsCompleted est faux, l'appel à EndInvoke devrait être bloqué, et tout le point de la poignée d'attente (comme pour le rappel) est de savoir quand le résultat est prêt et ne bloque pas. Ainsi, le premier IsCompleted est défini sur true, puis le handle d'attente est signalé, puis le rappel est appelé. Voyez comment l'exemple de Jeffrey Richter le fait. Toutefois,, vous devriez probablement essayer d'éviter les hypothèses sur l'ordre dans lequel ces trois méthodes (interrogation, wait handle, callback) peuvent détecter l'achèvement, car il est possible de les implémenter dans un ordre différent de celui prévu. 4: Je ne peux pas vous aider, sauf que vous pourriez trouver la réponse en déboguant dans le code source de l'implémentation qui vous intéresse. Ou vous pourriez probablement proposer une expérience pour découvrir ... ou mettre en place une bonne expérience et déboguer dans la source du framework pour être vraiment sûr.

Questions connexes