54 Stimmen

Das Starten von Aufgaben in einer foreach-Schleife verwendet den Wert des letzten Elements.

Ich versuche es zum ersten Mal, mit den neuen Tasks zu arbeiten, aber es passiert etwas, das ich nicht verstehe.

Zuerst der Code, der ziemlich einfach ist. Ich übergebe eine Liste von Pfaden zu einigen Bilddateien und versuche, einen Task hinzuzufügen, um jeden von ihnen zu verarbeiten:

public Boolean AddPictures(IList paths)
{
    Boolean result = (paths.Count > 0);
    List tasks = new List(paths.Count);

    foreach (string path in paths)
    {
        var task = Task.Factory.StartNew(() =>
            {
                Boolean taskResult = ProcessPicture(path);
                return taskResult;
            });
        task.ContinueWith(t => result &= t.Result);
        tasks.Add(task);
    }

    Task.WaitAll(tasks.ToArray());

    return result;
}

Ich habe festgestellt, dass, wenn ich dies einfach so laufen lasse, z.B. mit einer Liste von 3 Pfaden in einem Unit-Test, alle drei Tasks den letzten Pfad in der bereitgestellten Liste verwenden. Wenn ich durchgehe (und die Verarbeitung der Schleife verlangsame), wird jeder Pfad aus der Schleife verwendet.

Kann mir bitte jemand erklären, was passiert und warum? Mögliche Lösungsansätze?

3 Stimmen

Darf ich vorschlagen, ReSharper zu verwenden. Dieser spezielle Fehler und andere potenzielle Bugs werden für Sie hervorgehoben.

102voto

Jon Skeet Punkte 1325502

Du schließt über die Schleifenvariable. Tu das nicht. Nimm stattdessen eine Kopie:

foreach (string path in paths)
{
    string pathCopy = path;
    var task = Task.Factory.StartNew(() =>
        {
            Boolean taskResult = ProcessPicture(pathCopy);
            return taskResult;
        });
    // Siehe Hinweis am Ende des Beitrags
    task.ContinueWith(t => result &= t.Result);
    tasks.Add(task);
}

Dein aktueller Code erfasst path - nicht den Wert davon, wenn du die Aufgabe erstellst, sondern die Variable selbst. Diese Variable ändert ihren Wert jedes Mal, wenn du die Schleife durchläufst - daher kann sie sich leicht ändern, bis dein Delegierter aufgerufen wird.

Indem du eine Kopie der Variable machst, führst du jedes Mal, wenn du die Schleife durchläufst, eine neue Variable ein - wenn du diese Variable erfasst, wird sie in der nächsten Iteration der Schleife nicht geändert.

Eric Lippert hat ein paar Blog-Beiträge, die dies ausführlicher behandeln: Teil 1; Teil 2.

Fühle dich nicht schlecht - das passiert fast allen :(


Hinweis zu dieser Zeile:

task.ContinueWith(t => result &= t.Result);

Wie in den Kommentaren bereits erwähnt wurde, ist dies nicht thread-sicher. Mehrere Threads könnten sie gleichzeitig ausführen und möglicherweise die Ergebnisse überschreiben. Ich habe keine Sperre oder Ähnliches hinzugefügt, da dies vom Hauptproblem ablenken würde, an dem die Frage interessiert ist, nämlich der Variablenerfassung. Es ist jedoch wichtig, sich dessen bewusst zu sein.

1 Stimmen

Aber sicher. Wald vor lauter Bäumen und so. :)

1 Stimmen

Dieses Schließungsproblem und die falsche Verwendung von Random() müssen in Bezug auf die Häufigkeit unter den Top 5 bei SO liegen

0 Stimmen

Bitte beachten Sie, dass dieser "Fehler" (der ursprünglich absichtlich war) in C#5.0 behoben sein soll.

12voto

bdukes Punkte 144019

Der Lambda-Ausdruck, den du an StartNew übergibst, bezieht sich auf die path-Variable, die sich bei jeder Iteration ändert (d.h. dein Lambda nutzt die Referenz von path und nicht nur ihren Wert). Du kannst eine lokale Kopie davon erstellen, damit du nicht auf eine Version zeigst, die sich ändern wird:

foreach (string path in paths)
{
    var lambdaPath = path;
    var task = Task.Factory.StartNew(() =>
        {
            Boolean taskResult = ProcessPicture(lambdaPath);
            return taskResult;
        });
    task.ContinueWith(t => result &= t.Result);
    tasks.Add(task);
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X