Using anonymous methods inside a task with a delay inside the loop

I have the following -

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

The problem is that the value i passed to ProcessResult is represented by the value when it starts, and not by the iteration value when it is created.

What is the best way to protect against this?

+5
source share
3 answers

You need to write the value of i to your own variable.

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

Example:

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

This outputs something like:

 0 2 1 4 3 

But this:

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

Outputs:

 5 5 5 5 5 
+2
source

You need to create a temporary variable inside the loop; in your current code, you write the variable i , not the value, which means that when the continuation tasks are finally completed, the cycle is already completed, and i is N-1 .

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

A lambda that uses an external variable actually captures the variable, not the value stored in it. This means that as the loop continues, the value you read from the captured variable will change.

You can fix this using the temp variable inside the loop. Your code will be much cleaner if you used async/await instead of ContinueWith and lambdas, for example:

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

In general, you can avoid capture problems by copying the loop variable to the variable defined inside the loop area.

This fixes the problem since a temporary variable exists only inside the loop body. A lambda is also created inside the loop body and captures a local immutable 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(); } 

Even better, to avoid capture and pass the loop value as a ContinueWith state parameter, for example:

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

Source: https://habr.com/ru/post/1262679/


All Articles