Я думаю, що оптимізатор обдурить відсутність "мінливого" ключового слова на isCompleteзмінній.
Звичайно, ви не можете його додати, оскільки це локальна змінна. І звичайно, оскільки це локальна змінна, вона взагалі не потрібна, бо місцеві жителі тримаються на стеці і вони, природно, завжди "свіжі".
Однак після компіляції це вже не є локальною змінною . Оскільки доступ до нього є анонімним делегатом, код розбивається, і він переводиться в поле допоміжної категорії та члена, щось на зразок:
public static void Main(string[] args)
{
TheHelper hlp = new TheHelper();
var t = new Thread(hlp.Body);
t.Start();
Thread.Sleep(500);
hlp.isComplete = true;
t.Join();
Console.WriteLine("complete!");
}
private class TheHelper
{
public bool isComplete = false;
public void Body()
{
int i = 0;
while (!isComplete) i += 0;
}
}
Зараз я можу уявити, що компілятор / оптимізатор JIT у багатопотоковому середовищі при обробці TheHelperкласу може насправді кешувати значення falseв якомусь регістрі чи кадру стека на початку Body()методу і ніколи не оновлювати його, поки метод не закінчиться. Це тому, що НІМАЄ ГАРАНТІЇ, що нитка і метод НЕ закінчуються до того, як "= true" не буде виконано, тому якщо немає гарантії, то чому б не кешувати його та отримати підвищення продуктивності читання об'єкта купи один раз, а не читати його на кожному ітерація.
Саме тому ключове слово volatileіснує.
Щоб цей клас-помічник був правильним, трохи краще 1) у багатопотокових середовищах він повинен мати:
public volatile bool isComplete = false;
але, звичайно, оскільки це автогенерований код, ви не можете його додати. Кращим підходом було б додати lock()читання навколо читання та запису isCompletedабо використовувати інші готові до використання утиліти для синхронізації чи нарізки / виконання завдань замість того, щоб намагатися робити це голим металом (що це не буде голим металом, оскільки це C # на CLR з GC, JIT та (..)).
Різниця в режимі налагодження виникає, ймовірно, тому, що в режимі налагодження виключається багато оптимізацій, тож ви можете налагодити код, який ви бачите на екрані. Тому while (!isComplete)не оптимізовано, тому ви можете встановити там точку розриву, і тому isCompleteне агресивно кешується в регістрі чи стеку при запуску методу і зчитується з об'єкта на купі при кожній ітерації циклу.
До речі. Це лише мої здогадки про це. Я навіть не намагався її скласти.
До речі. Це не здається помилкою; це більше схоже на дуже незрозумілий побічний ефект. Крім того, якщо я маю рацію щодо цього, то це може бути дефіцит мови - C # повинен дозволяти розміщувати "мінливе" ключове слово на локальних змінних, які фіксуються та просуваються до полів учасників у закритих місцях.
1) дивись нижче за коментарями Еріка Ліпперта про volatileта / або в цьому дуже цікаву статтю з зазначенням рівня складності , що беруть участь в забезпеченні того , щоб код , спираючись на volatileце безпечно ..uh, добре ..uh, скажімо OK.