2016-04-13 2 views
2

Я написал блок кода, но я не уверен, что он потокобезопасен.C# резьба безопасная крышка внутри foreach

List<Task> tasks = new List<Task>(); 
foreach (KeyValuePair<string, string> kvp in result) 
{ 
    var t = new Task(async() => 
    { 
     int retries = 0; 
     bool success = false; 
     try 
     { 
      while (retries <= _maxRetries && !success) 
      { 
       await doSomething(kvp.Value); 
       success = true; 
      } 
     } 
     catch (Exception e) 
     { 
      retries++; 
     } 

     if (retries == _maxRetries) 
     { 
      //TODO: need to do smth about it 
     } 
    }); 
    tasks.Add(t); 
    t.Start(); 
} 
await Task.WhenAll(tasks); 

Могу ли я рассчитывать на то, что, когда компилятор ставит задачу он использует безопасное значение, что означает, что до тех пор, как Im в цикле и задачи еще не указано, значения, установленные в порядке ?

Потому что, я думаю, что после первого повтора цикла while объект kvp не будет таким, каким он был, когда задача выполнялась в первый раз.

Если его на самом деле не является потокобезопасным, и я думаю, что это действительно так, как его можно исправить?

+0

Какая версия .NET это? –

+0

@ LasseV.Karlsen im using .net Framework 4.5 –

ответ

2

Если вы на C# 5, то ваш код в порядке; семантика foreach была изменена так, что переменная цикла логически привязана к каждой итерации. Per Eric Lippert:

В C# 5, переменная петля Еогеасп будет логически внутри цикла, и поэтому закрытие закроется через новую копию переменной каждый раз.

Если вы находитесь на C# 4 или более ранней версии, то скопируйте kvp в локальную переменную для закрытия.

Ваш код имеет несвязанную ошибку: задача, инициализированная с помощью конструктора Task, не будет ждать завершения делегата асинхронной функции, который передается ему как аргумент. Вместо этого вы должны использовать Task.Run для таких делегатов.

Существует более простой (безопасный) способ достижения этой цели:

var tasks = result.Select(kvp => Task.Run(async() => 
{ 
    int retries = 0; 
    bool success = false; 
    // ... 
})).ToList(); 

await Task.WhenAll(tasks); 
+0

, не будет ли локальная переменная просто использоваться в качестве ссылки на обычный kvp? –

+0

@OriRefael: Да, но захваченная локальная переменная не будет обновляться при последующих итерациях цикла со ссылками на * другие экземпляры * KeyValuePair. – Douglas

1

Я думаю, вы должны сохранить КВП в локальной переменной внутри вашей задачи, как это:

var t = new Task(async() => 
    { 
     var localKvp = kvp; 
     .... 
    } 

Таким образом, он будет ловить локальную переменную для каждой задачи

+0

, но он просто сохранит ссылку на него, потому что его объект, поэтому он ничего не сделает .. –

+0

@OriRefael Неверно, он создаст новую * переменную *, которая будет захвачена делегатом вместо переменной цикла. Эта новая переменная будет обрабатываться как «новая» каждый раз, когда цикл повторяется, так что на самом деле у вас будет одна такая переменная на итерацию, тогда как переменная цикла будет существовать только один раз для всех итераций. Это было изменено в последних версиях C#. –

+0

это будет уникальная ссылка для каждой задачи, которая ссылается на свой объект kvp – Mitklantekutli

Смежные вопросы