11
votes

Tâche . La concaténation et la cordes de cordes

Je jouais avec async / attendre lorsque je suis tombé sur ce qui suit: xxx

Je m'attendais à ce que le résultat soit "12345", mais c'était "1235 ". En quelque sorte '4' a été mangé.

si je divisait la ligne x en: xxx

alors les résultats attendus "12345". < p> Pourquoi est-ce tellement? (En utilisant vs2012)


1 commentaires

Le pourquoi est certainement intéressant, mais il convient également de noter que vous ne devez pas faire cela - l'accès partagé à une variable avec des opérations qui ne sont pas atomiques sans serrures ni autre logique de threading ne sont sûrs que de vous mettre dans des situations indéterminées.


3 Réponses :


10
votes

Pourquoi est-ce tellement? (En utilisant vs2012)

Vous l'exécutez dans une application de console, ce qui signifie qu'il n'y a pas de contexte de synchronisation actuel.

En tant que telle, la partie de la méthode FOOASYNC () après que le attend est exécuté dans un fil séparé. Lorsque vous faites str + = t.result Vous faites une condition de course entre le + = 4 appel et le + = t.result . C'est parce que string + = n'est pas une opération atomique.

Si vous deviez exécuter le même code dans une application Windows Forms ou WPF, le contexte de synchronisation serait capturé et utilisé pour le + = "4" , ce qui signifie que cela fonctionnerait tous sur le même fil, et vous ne verriez pas ce problème.


3 commentaires

Comment peut-il y avoir une condition de course si renvoie 5 se passe après str + = 4 dans le code de FOOASYNC ?


@Ilakogan parce que + = n'est pas une opération atomique. Il est divisé en plusieurs sous-opérations qui peuvent (et dans ce cas sont) être entrelacées pour générer des résultats indéfinis.


@Ilakogan La condition de course se produit car str + = t.result est effectivement str = str + t.result et "str" ​​est extrait avant + = 4 finitions.



7
votes

C # déclarations du formulaire x + = y; sont étendus à x = x + y; au moment de la compilation.

str + = t.result; devient str = str + t.result; , où str est lu avant < / em> obtenir t.result . À ce stade, str est "123" . Lorsque la suite dans FOOASYNC est exécutée, elle modifie str , puis renvoie 5 . Donc str est maintenant "1234" . Mais alors la valeur de str qui a été lu avant la suite dans FOOASYNC RAN (qui est "123" ) est concaténé avec 5 pour attribuer à str la valeur "1235" .

Lorsque vous le cassez en deux déclarations, int i = t.result; str + = i; , ce comportement ne peut pas arriver.


1 commentaires

Le comportement peut encore se produire lorsqu'il est divisé en deux déclarations. C'est juste moins probable. Voir la réponse de Reed.



6
votes

C'est une condition de race. Vous ne synchronisez pas correctement l'accès à la variable partagée entre les deux threads d'exécution que vous avez.

Votre code fait probablement quelque chose comme ceci:

  • Définit la chaîne pour être "1"
  • appelez FOOASYNC
  • APPEND 2
  • Lorsque l'attente est appelée la méthode principale continue d'exécuter, le rappel dans FOOASYNC sera exécuté dans le pool de fil; à partir de là, les choses sont indéterminées.
  • Le fil principal ajoute 3 à la chaîne

    Ensuite, nous arrivons à la ligne intéressante: xxx

    Ici, il est divisé en plusieurs opérations plus petites. Il va d'abord obtenir la valeur actuelle de str . À ce stade, la méthode ASYNC (dans toutes les probabilités) n'a pas encore fini, ce sera donc "123" . Il attend ensuite que la tâche achète (car résultat force une attente de blocage) et ajoute le résultat de la tâche, dans ce cas 5 à la fin de la chaîne. < / p>

    Le rappel ASYNC aura attrapé et re-définir str après le fil principal avait déjà saisi la valeur actuelle de STR , puis il écrasera str sans qu'il soit lu comme le fil principal est bientôt pour écraser.


0 commentaires