IAsyncResult.AsyncWaitHandle.WaitOne() se termine à l'avance de rappel
Voici le code:
class LongOp
{
//The delegate
Action longOpDelegate = LongOp.DoLongOp;
//The result
string longOpResult = null;
//The Main Method
public string CallLongOp()
{
//Call the asynchronous operation
IAsyncResult result = longOpDelegate.BeginInvoke(Callback, null);
//Wait for it to complete
result.AsyncWaitHandle.WaitOne();
//return result saved in Callback
return longOpResult;
}
//The long operation
static void DoLongOp()
{
Thread.Sleep(5000);
}
//The Callback
void Callback(IAsyncResult result)
{
longOpResult = "Completed";
this.longOpDelegate.EndInvoke(result);
}
}
Ici, c'est le cas de test:
[TestMethod]
public void TestBeginInvoke()
{
var longOp = new LongOp();
var result = longOp.CallLongOp();
//This can fail
Assert.IsNotNull(result);
}
Si c'est exécuter les cas de test peut échouer. Pourquoi exactement?
Il y a très peu de documentation sur la façon de déléguer.BeginInvoke œuvres. Quelqu'un aurait-il des idées qu'ils aimeraient partager?
Mise à jour
C'est un subtil course-condition qui n'est pas bien documentée dans MSDN ou ailleurs. Le problème, comme expliqué dans la accepté de répondre, c'est que lorsque l'opération est terminée l'Attente de la Poignée est signalée, et puis le Rappel est exécuté. Le signal communiqués de l'attente thread principal et maintenant le Rappel d'exécution entre dans la "course". Jeffry de Richter a suggéré la mise en œuvre montre ce qui se passe derrière les coulisses:
//If the event exists, set it
if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();
//If a callback method was set, call it
if (m_AsyncCallback != null) m_AsyncCallback(this);
Pour une solution reportez-vous à Ben Voigt réponse. Que la mise en œuvre n'a pas à assumer la charge supplémentaire d'un deuxième attendre poignée.
si vous remarquez que la question n'est pas de se demander "Comment puis-je obtenir que cela fonctionne?" Clairement, c'est un exemple artificiel.
Votre question est: "Si c'est exécuter les cas de test peut échouer. Pourquoi exactement?". J' répondre. Parce que vous essayer de mélanger deux façons très différentes de traitement d'une opération asynchrone.
Il dépend du Sommeil (argument)? Je ne suis pas l'achat. Post repro que n'importe qui peut essayer de l'exécuter sur leur machine sans un test runner.
vous pouvez juste essayer var longOp = new LongOp(); partout où vous le souhaitez. Mais vous devez savoir que?!
OriginalL'auteur Noel Abrahams | 2010-11-04
Vous devez vous connecter pour publier un commentaire.
La ASyncWaitHandle.WaitOne() est signalé lorsque l'opération asynchrone se termine. Dans le même temps CallBack() est appelée.
Cela signifie que le code après WaitOne() est exécutée dans le thread principal et le Rappel est exécuté dans un autre thread (probablement le même qui s'exécute DoLongOp()). Il en résulte une situation de concurrence où la valeur de longOpResult est essentiellement connue à l'heure qu'il est retourné.
On pourrait s'attendre à ce que ASyncWaitHandle.WaitOne() aurait été signalé lorsque la fonction de Rappel a été fini, mais c'est juste pas comment il fonctionne 😉
Vous aurez besoin d'un autre ManualResetEvent avoir le thread principal d'attendre le Rappel pour définir longOpResult.
OriginalL'auteur Torben Rahbek Koch
Comme d'autres l'ont dit,
result.WaitOne
signifie simplement que la cible deBeginInvoke
a fini, et non pas la fonction de rappel. Donc suffit de mettre le post-traitement de code dans laBeginInvoke
délégué.Il est intelligent, mais quand serait-ce réellement utile? ManualReset vous donne le contrôle d'attendre à chaque fois que vous voulez attendre, cela demande de l'opération puis un rappel pour gérer le résultat et attend les deux à la fois. Vous pouvez simplement gérer le résultat de l'opération elle-même, si c'est ce que tu voulais.
Cela permet de gérer le résultat, même si vous n'avez pas à écrire la fonction en peluche dans
longOpDelegate
(ou c'est une méthode d'une autre classe, et n'a pas accès à la privé " longOpResult` membre, ou vous ne voulez pas introduire inverse de couplage, ou ...).Bon point, si vous n'avez pas accès au code de la longop méthode, c'est miles mieux. Je vais laisser mon extrait en place même si, comme un exemple de manualreset puisque beaucoup l'ont mentionné.
merci. Mais alors nous disons que le IAsyncResult renvoyé par BeginInvoke est complètement inutile, et, en fait, doivent venir avec un avertissement "NE PAS UTILISER!", non?
OriginalL'auteur Ben Voigt
Ce qui se passe
Depuis votre opération
DoLongOp
a terminé, contrôler reprend dansCallLongOp
et la fonction se termine avant que l'opération de Rappel est terminé.Assert.IsNotNull(result);
puis l'exécute avantlongOpResult = "Completed";
.Pourquoi? AsyncWaitHandle.WaitOne() aura plus qu'à attendre pour votre opération asynchrone, votre Rappel
Le rappel paramètre de BeginInvoke est en fait un AsyncCallback délégué, ce qui signifie que votre fonction de rappel est appelée de façon asynchrone. C'est par la conception, le but est de traiter les résultats de l'opération asynchrone (et est tout l'objet de ce rappel à l'paramètre).
Depuis le BeginInvoke fonction appelle la fonction de Rappel de la IAsyncResult.WaitOne appel est juste pour l'opération et n'a pas d'influence sur la fonction de rappel.
Voir le La documentation de Microsoft (section de l'Exécution d'une Méthode de Rappel Lorsqu'un Appel Asynchrone se Termine). Il y a aussi une bonne explication et exemple.
Solution
Si vous voulez attendre pour le fonctionnement et la fonction de rappel, vous devez gérer la signalisation de vous-même. Un ManualReset est une façon de faire qui vous donne certainement le plus de contrôle (et c'est la façon dont Microsoft a fait ses docs).
Ici est modifié le code à l'aide ManualResetEvent.
Pour l'exemple que vous avez donné, vous feriez mieux de ne pas utiliser un rappel et au lieu de manipuler le résultat dans votre CallLongOp fonction, dans ce cas, votre WaitOne sur le fonctionnement délégué fonctionnera très bien.
Vous pouvez l'utiliser pour arrêter l'exécution de la méthode qui a appelé begininvoke. I. e. Tous les scénarios où vous voulez attendre pour l'opération elle-même pour terminer.
CONDITION DE COURSE! Vous feriez mieux de créer l'événement avant d'appeler
BeginInvoke
, mais en ajoutant plus d'objets de synchronisation est inutile et inefficace.Fixe (mais c'était une correction mineure et le downvote a été dur!). D'habitude, ManualResetEvent est la façon dont c'est fait dans la documentation. Avez-vous une meilleure façon de la synchronisation la fonction de rappel?
OriginalL'auteur badbod99
Le rappel est exécutée après la CallLongOp méthode. Puisque vous ne réglez la valeur de la variable dans la fonction de rappel, il est évident qu'il serait nulle.
À lire :texte du lien
merci pour votre reponse. Le rappel n'est pas toujours exécutée après la CallLongOp méthode. Essayez de mettre de Fil.Sleep(500); dans CallLongOp avant de retourner longOpResult; et le test passe.
OriginalL'auteur Kell
J'ai eu le même problème récemment, et j'ai pensé à un autre moyen de le résoudre, cela a fonctionné dans mon cas. Bacially si le délai n'est pas borther vous, vérifiez à nouveau le drapeau IsCompleted Attendre quand la Poignée est en timeout. Dans mon cas, l'attente de la poignée est signalé avant de bloquer le thread, et juste après la condition si, afin de le réactiver après la temporisation fera l'affaire.
OriginalL'auteur zhang lin