7

同時に複数のタスクを実行しようとしていますが、理解も解決もできないような問題に遭遇しました。

私はこのような機能を持っていました:

private void async DoThings(int index, bool b) {
    await SomeAsynchronousTasks();
    var item = items[index];
    item.DoSomeProcessing();
    if(b)
        AVolatileList[index] = item; //volatile or not, it does not work
    else
        AnotherVolatileList[index] = item;
}

forを使用してループで呼び出したかったことTask.Run()。ただし、これにパラメーターを送信する方法が見つかりませんAction<int, bool>でした。同様の場合にラムダを使用することをお勧めします。

for(int index = 0; index < MAX; index++) { //let's say that MAX equals 400 
    bool b = CheckSomething();
    Task.Run(async () => {
        await SomeAsynchronousTasks();
        var item = items[index]; //here, index is always evaluated at 400
        item.DoSomeProcessing();
        if(b)
            AVolatileList[index] = item; //volatile or not, it does not work
        else
            AnotherVolatileList[index] = item;
    }
}

ラムダでローカル変数を使用すると、その値が「キャプチャ」されると思いましたが、そうではないようです。for値がループの最後にキャプチャされるかのように、常に index の値を取ります。変数は各反復でラムダで 400 で評価されるindexので、もちろんIndexOutOfRangeException400 回になります (items.Count実際には ですMAX)。

私はここで何が起こっているのか本当にわかりません (私はそれについて本当に興味がありますが)、私が達成しようとしていることを行う方法もわかりません。どんなヒントでも大歓迎です!

4

2 に答える 2

7

インデックス変数のローカル コピーを作成します。

for(int index = 0; index < MAX; index++) {
  var localIndex = index;
  Task.Run(async () => {
    await SomeAsynchronousTasks();
    var item = items[index];
    item.DoSomeProcessing();
    if(b)
        AVolatileList[index] = item;
    else
        AnotherVolatileList[index] = item;
  }
}

これは、C# がforループを実行する方法によるものindexです。更新される変数は 1 つだけであり、すべてのラムダが同じ変数をキャプチャしています (ラムダでは、値ではなく変数がキャプチャされます)。

補足として、次のことをお勧めします。

  1. 避けてくださいasync void。メソッドがいつasync void完了するかを知ることはできず、エラー処理のセマンティクスが困難です。
  2. awaitすべての非同期操作。つまり、 から返されたタスクを無視しないでくださいTask.RunTask.WhenAllなどに使用しawaitてください。これにより、例外が伝搬されます。

たとえば、次のような使用方法がありますWhenAll

var tasks = Enumerable.Range(0, MAX).Select(index =>
  Task.Run(async () => {
    await SomeAsynchronousTasks();
    var item = items[localIndex];
    item.DoSomeProcessing();
    if(b)
        AVolatileList[localIndex] = item;
    else
        AnotherVolatileList[localIndex] = item;
  }));
await Task.WhenAll(tasks);
于 2013-07-10T13:41:21.413 に答える
2

すべてのラムダは、ループ変数である同じ変数をキャプチャします。ただし、すべてのラムダは、ループが終了した後にのみ実行されます。その時点で、ループ変数は最大値を持つため、すべてのラムダがそれを使用します。

Stephen Cleary は、彼の回答でそれを修正する方法を示しています。

Eric Lippert は、これについて詳細な 2 部構成のシリーズを書きました。

于 2013-07-10T13:44:17.090 に答える