2017-01-12 3 views
5

ich folgendes haben -Verwendung anonymer Methoden innerhalb einer verzögerten Aufgabe innerhalb einer Schleife

for (int i = 0; i < N; ++i) 
{ 
    var firstTask = DoSomething(...); 

    var task = anotherTask.ContinueWith(
     (t) => ProcessResult(i, t.Result)); 
} 

Problem ist, dass der Wert von i in ProcessResult vergangen scheint, den Wert zu sein, wenn es beginnt, den Wert von die Iteration, wenn es erstellt wird.

Was ist der beste Schutz dagegen?

+0

Warum benutzen Sie 'ContinueWith' anstatt zu warten? Wie für "i" erfasst Ihr Lambda die * Variable * nicht den Wert der Variablen. Das Lesen von "i" gibt das zurück, was "i" enthält, wenn Sie es tatsächlich lesen - wenn der Aufruf von 'ProcessResult (i, ..)' tatsächlich ausgeführt wird. Dies ist übrigens das erwartete Verhalten. Mit 'erwarten' wird dies behoben, indem Sie das Lambda loswerden * und * Ihren Code vereinfachen –

+0

Um die Aufgaben zu verketten. Diese können lange laufen, während warten würde, den aktuellen Thread zu suspendieren. – Hector

+0

Nein, wird es nicht. 'erwarten' * wartet *, es blockiert nicht.Es ist das Äquivalent von 'ContinueWith', nicht' Wait'. Es macht das Verketten * viel * einfacher * weil es keine Lambdas und Captures benötigt. –

Antwort

2

Sie müssen den Wert i in einer eigenen Variablen erfassen.

for (int i = 0; i < N; ++i) 
{ 
    var count = i; 

    var firstTask = DoSomething(...); 

    var task = anotherTask.ContinueWith(
     (t) => ProcessResult(count, t.Result)); 
} 

Beispiel:

for (int i = 0; i < 5; ++i) 
{ 
    var a = i; 
    var task = Task.Delay(0).ContinueWith((t) => a.Dump()); 
} 

Dies gibt so etwas wie:

0 
2 
1 
4 
3 

Aber:

for (int i = 0; i < 5; ++i) 
{ 
    var task = Task.Delay(0).ContinueWith((t) => i.Dump()); 
} 

Ausgänge:

5 
5 
5 
5 
5 
2

Sie müssen eine temporäre Variable innerhalb der Schleife erstellen; In Ihrem aktuellen Code erfassen Sie die Variable i, nicht den Wert, was bedeutet, dass, wenn die Fortsetzungsaufgaben schließlich ausgeführt werden, die Schleife bereits abgeschlossen ist und iN-1 ist.

for (int i = ...) 
{ 
    var temp = i; 
    var task = anotherTask.ContinueWith(t => ProcessResult(temp, t.Resume)); 
} 
+1

Oder verwenden Sie "erwarten" statt "ContinueWith" –

+1

@ PanagiotisKanavos ja, aber die Frage ist, warum der Code verhält sich so, wie es tut, nicht, wenn dies der beste Weg, um den Job zu tun ist. – InBetween

+0

Danke! Ich nehme an, der Compiler/Garbage Collector kann mit der Schleife weitermachen und die Variable wird nicht entsorgt? – Hector

2

Ein Lambda, das eine äußere Variable verwendet, erfasst tatsächlich die Variable, nicht den darin gespeicherten Wert. Das bedeutet, dass mit fortschreitender Schleife auch der Wert geändert wird, den Sie aus der erfassten Variablen lesen würden.

Sie können dies beheben, indem Sie eine temporäre Variable in der Schleife verwenden. Ihr Code wäre viel sauberer sein aber wenn Sie async/await statt ContinueWith und Lambda-Ausdrücke verwendet werden, zB:

for (int i=0;i<N;i++) 
{ 
    //... 
    var result=await thatOtherAsyncMethod(...); 
    ProcessResult(i, result)); 
} 

In der Regel können Sie Capture-Probleme vermeiden, durch Kopieren des Schleifenvariable in eine Variable innerhalb der Schleife des Oszilloskops definiert.

Dies behebt das Problem, da die temporäre Variable nur innerhalb des Körpers der Schleife existiert. Die Lambda ist auch innerhalb der Schleife des Körpers erstellt und erfasst eine lokale, unveränderliche Variable:

for (int i=0;i<N;i++) 
{ 
    var temp=i; 
    var myLambda = new Action(()=>MyMethod(temp)); 

    //This runs with the local copy, not i 
    myLambda(); 
} 

Ein noch besserer Weg aber ist zu vermeiden erfasst und die Schleife Wert als Zustandsparameter zu ContinueWith passieren, zB:

for (int i = 0; i < N; ++i) 
{ 
    //... 
    var task = anotherTask.ContinueWith(
           (t,state) => ProcessResult((int)state, t.Result), 
           i); 
    //... 
}