Как избежать захваченных переменных?
У меня проблема с
foreach(var category in categories)
{
foreach(var word in words)
{
var waitCallback = new WaitCallback(state =>
{
DoSomething(word, category);
});
ThreadPool.QueueUserWorkItem(waitCallback);
}
}
Когда DoSomething
выполняется, он получает последнее значение для каждой захваченной переменной вместо желаемого значения. Я могу представить себе решение для этого, но представьте, что вы, ребята, можете придумать лучшие решения
4 ответа
Решение
Канонический способ решить эту проблему - скопировать значения во временные переменные, которые объявлены внутри цикла.
foreach(var category in categories)
{
var catCopy = category;
foreach(var word in words)
{
var wordCopy = word;
var waitCallback = new WaitCallback(state =>
{
DoSomething(wordCopy, catCopy);
});
ThreadPool.QueueUserWorkItem(waitCallback);
}
}
Рефакторинг это:
foreach(var category in categories) {
foreach(var word in words) {
DoSomethingAsync(word, category);
}
}
...
private void DoSomethingAsync(string word, string category) {
var waitCallback = new WaitCallback(state => DoSomething(word, category));
ThreadPool.QueueUserWorkItem(waitCallback);
}
Это просто и легко понять. В нем говорится о намерениях разработчика, не загромождая код дополнительными переменными (как по умолчанию для решения этой проблемы).
Я бы написал все это примерно так, что уклоняется от проблемы и не оставляет абсолютно никаких вопросов о том, что происходит:
var callbacks = words.SelectMany(w => categories.Select(c =>
new WaitCallback(state => {
DoSomething(w, c);
})
));
foreach (var callback in callbacks)
ThreadPool.QueueUserWorkItem(callback);
Для справки, я думаю, что следующее решило бы мою проблему:
foreach(var category in categories)
{
foreach(var word in words)
{
var waitCallback = new WaitCallback(state =>
{
var kv = (KeyValuePair<string, string>)state;
DoSomething(kv.Key, kv.Value);
});
var state2 = new KeyValuePair<string, string>(word, category);
ThreadPool.QueueUserWorkItem(waitCallback, state2);
}
}