2017-01-12 10 views
5

Tôi đã sau -Sử dụng phương pháp vô danh bên trong một nhiệm vụ bị trì hoãn trong vòng lặp

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

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

Vấn đề là rằng giá trị của i thông qua vào ProcessResult dường như là giá trị khi nó bắt đầu, không phải là giá trị của lặp lại khi nó được tạo ra.

Cách tốt nhất để bảo vệ chống lại điều này là gì?

+0

Tại sao bạn sử dụng 'ContinueWith' thay vì chờ? Đối với 'i' lambda của bạn nắm bắt * biến * không phải là giá trị của biến. Việc đọc 'i' sẽ trả về bất cứ thứ gì' i' chứa khi bạn thực sự đọc nó - khi cuộc gọi đến 'ProcessResult (i, ..)' thực sự được thực thi. Đây là hành vi mong đợi bằng cách này. Sử dụng 'await' sẽ sửa lỗi này bằng cách loại bỏ lambda * và * đơn giản hóa mã của bạn –

+0

Để kết nối các tác vụ. Đây có thể là chạy dài, trong khi chờ đợi sẽ đình chỉ các chủ đề hiện tại. – Hector

+0

Không, không. 'await' * đang chờ *, nó không chặn.Nó tương đương với 'ContinueWith', không phải' Wait'. Nó làm cho chuỗi * nhiều * dễ dàng hơn * bởi vì nó không yêu cầu lambdas và chụp. –

Trả lời

2

Bạn cần nắm bắt giá trị i thành biến riêng.

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

    var firstTask = DoSomething(...); 

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

Ví dụ:

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

này kết quả đầu ra một cái gì đó như:

0 
2 
1 
4 
3 

Nhưng điều này:

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

Đầu ra:

5 
5 
5 
5 
5 
2

Bạn cần tạo biến tạm thời trong vòng lặp; trong mã hiện tại của bạn, bạn đang chụp biến i, không phải là giá trị, có nghĩa là khi các tác vụ tiếp tục được thực hiện cuối cùng, vòng lặp đã kết thúc và iN-1.

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

Hoặc sử dụng 'await' thay vì' ContinueWith' –

+1

@PanagiotisKanavos yes, nhưng câu hỏi đặt ra là tại sao mã hoạt động theo cách của nó, không phải nếu đây là cách tốt nhất để thực hiện công việc. – InBetween

+0

Cảm ơn! Tôi lấy nó trình biên dịch/thu rác có thể xử lý các vòng lặp di chuyển trên và biến sẽ không được xử lý? – Hector

2

Một lambda sử dụng biến ngoài thực sự nắm bắt biến, chứ không phải giá trị được lưu trữ trong biến đó. Điều đó có nghĩa là khi tiền tố lặp lại, giá trị bạn sẽ đọc từ biến bị bắt cũng thay đổi.

Bạn có thể sửa lỗi này bằng cách sử dụng biến tạm thời bên trong vòng lặp. Mã của bạn sẽ có rất nhiều bụi tuy nhiên nếu bạn sử dụng async/await thay vì ContinueWith và lambdas, ví dụ:

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

Nói chung, bạn có thể tránh được vấn đề chụp bằng cách sao chép các biến vòng lặp thành một biến được định nghĩa bên trong phạm vi của vòng lặp.

Điều này khắc phục sự cố vì biến tạm thời chỉ tồn tại bên trong phần thân của vòng lặp. Lambda là cũng tạo ra bên trong cơ thể của vòng lặp và chụp một địa phương, biến không thay đổi:

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(); 
} 

Một thậm chí cách tốt hơn, mặc dù là để tránh chụp và vượt qua giá trị vòng lặp như các tham số trạng thái để ContinueWith, ví dụ:

for (int i = 0; i < N; ++i) 
{ 
    //... 
    var task = anotherTask.ContinueWith(
           (t,state) => ProcessResult((int)state, t.Result), 
           i); 
    //... 
} 
Các vấn đề liên quan