14

L'erreur exacte:Index hors exception Plage lors de la création d'une tâche

Index was out of range. Must be non-negative and less than the size of the collection.

J'ai des tableaux d'index et les listes d'innombrables fois. J'ai utilisé des boucles avec des tableaux et des listes d'innombrables fois. Les données sont là, ça marche. Sauf quand j'essaye de créer une tâche pour ma fonction. Rappelez-vous, j'ai réussi cela avec une boucle foreach pour une fonction similaire; cette nouvelle nécessite deux arguments, donc je ne peux pas utiliser correctement une boucle foreach. Au moins, je ne pense pas que je peux.

Voici le code erroné:

if (addressList != null) { 
    textBox1.Text += ("Address List Length: " + addressList.Count + Environment.NewLine); 

    for (int i = 0; i < addressList.Count; i++) { 
     textBox1.Text += ("Task for " + addressList[i] + ":" + portList[i] + " initiated." + Environment.NewLine); 

     Task.Factory.StartNew(() => PingTaskAdapted(addressList[i], portList[i])); 
    }     
} 
else textBox1.Text = ("No IPs have been added."); 

En supposant addressList[0] est google.com et portList[0] est 80, Sortie:

Address List Length: 1 
Task for google.com:80 initiated. 

puis rupture du programme, avec Visual Studio me disant que, à PingTaskAdapted() J'appelle un index qui est hors de portée, quand il a littéralement imprimé les index en question, parce qu'ils existent.

Et juste pour être clair, si j'appelle PingTaskAdapted(addressList[0], pingList[0]); cela fonctionne sans aucun problème.

+0

Vous devez utiliser 'Enumerable.Zip'. – Alexander

Répondre

17

Votre tâche accèdera à la liste lorsque la tâche sera exécutée. Pas séquentiellement dans la ligne de code que vous regardez dans la boucle. Pour vous assurer que les valeurs correctes sont capturées dans la fermeture (et que les listes existent toujours et ont les mêmes valeurs), faites des copies locales en dehors de la tâche pour vous assurer que les valeurs sont capturées à ce moment:

var localAddress = addressList[i]; 
var localPort = portList[i]; 
Task.Factory.StartNew(() => PingTaskAdapted(localAddress , localPort)); 
+1

Je préférerais cette façon de faire, malgré ma propre réponse (que j'ai écrite comme je l'ai fait pour plus de clarté sur la variable qui a été modifiée). L'extrait de code de cette réponse rend clairement * quelle * valeur sera utilisée dans l'exécution de la tâche. –

+0

C'est un phénomène très étrange. Intéressant à apprendre sur. Mon code fonctionne en effet maintenant, merci. – soxroxr

7

Vous êtes victime de l'accès à la fermeture modifiée, comme il est si succinctement appelé. Fondamentalement, puisque vous utilisez une tâche - et un délégué pour démarrer - la valeur de i n'est pas garantie d'être ce que vous attendez de lui. Toutefois, si vous copiez i dans une variable locale spécifique à la portée d'une seule itération, cela devrait fonctionner.

for (int i = 0; i < addressList.Count; i++) 
{ 
    textBox1.Text += ("Task for " + addressList[i] + ":" + portList[i] + " initiated." + Environment.NewLine); 

    var iCopy = i; 
    Task.Factory.StartNew(() => PingTaskAdapted(addressList[iCopy], portList[iCopy])); 
} 


Cependant, comme l'a souligné dans this answer by nvoigt, il est beaucoup plus clair en matière de lisibilité et la maintenabilité si vous copiez les valeurs qui seront utilisées plutôt que la valeur de iterator.

+0

Avez-vous des références à ce sujet? Je tiens à lire à ce sujet. – thanatorr

+3

@thanatorr [ici] (https://stackoverflow.com/a/271447/767890) – InBetween

+0

Nous vous remercions de l'information supplémentaire sur l'accès aux modifications de fermeture. – soxroxr

5

Closures capturer les variables , non valeurs.

Modifier le code à ce qui suit, et vous verrez la question disparaître:

for (int i = 0; i < addressList.Count; i++) { 
    textBox1.Text += ("Task for " + addressList[i] + ":" + portList[i] + " initiated." + Environment.NewLine); 

    var temp = i; 
    Task.Factory.StartNew(() => PingTaskAdapted(addressList[temp], portList[temp])); 
    }