Я предполагаю, что оптимизатор введен в заблуждение из-за отсутствия ключевого слова volatile в 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()s вокруг операций чтения и записи isCompletedили использовать какие-либо другие готовые к использованию утилиты синхронизации или потоковой передачи / задания задач вместо того, чтобы пытаться сделать это с нуля (что не будет голым железом, так как это C # на CLR с GC, JIT и (..)).
Разница в режиме отладки возникает, вероятно, из-за того, что в режиме отладки исключается множество оптимизаций, поэтому вы можете отлаживать код, который видите на экране. Поэтому while (!isComplete)не оптимизирован, поэтому вы можете установить там точку останова, и поэтомуisComplete , не кэшируется агрессивно в регистре или стеке при запуске метода и считывается из объекта в куче на каждой итерации цикла.
КСТАТИ. Это всего лишь мои догадки. Я даже не пытался его скомпилировать.
КСТАТИ. Это не похоже на ошибку; это больше похоже на очень неясный побочный эффект. Кроме того, если я прав, тогда это может быть недостаток языка - C # должен позволять размещать ключевое слово volatile в локальных переменных, которые захватываются и продвигаются в поля-члены в замыканиях.
1) смотри ниже за комментариями Эрика Липпертом о volatileи / или в этом очень интересную статью с указанием уровня сложности , участвующих в обеспечении того , чтобы код , опираясь на volatileэто безопасно ..uh, хорошо ..uh, скажем OK.