Wydaje mi się, że optymalizator daje się nabrać na brak słowa kluczowego „volatile” w isCompletezmiennej.
Oczywiście nie możesz tego dodać, ponieważ jest to zmienna lokalna. I oczywiście, ponieważ jest to zmienna lokalna, w ogóle nie powinna być potrzebna, ponieważ lokalne pliki są trzymane na stosie i naturalnie zawsze są „świeże”.
Jednak po kompilacji nie jest już zmienną lokalną . Ponieważ dostęp do niego uzyskuje się przez anonimowego delegata, kod jest dzielony i tłumaczony na klasę pomocniczą i pole składowe, na przykład:
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;
}
}
Mogę sobie teraz wyobrazić, że kompilator / optymalizator JIT w środowisku wielowątkowym, podczas przetwarzania TheHelperklasy, może faktycznie buforować wartość falsew jakimś rejestrze lub ramce stosu na początku Body()metody i nigdy nie odświeża jej do momentu zakończenia metody. Dzieje się tak dlatego, że NIE MA GWARANCJI, że wątek i metoda NIE zakończą się przed wykonaniem "= true", więc jeśli nie ma gwarancji, to dlaczego nie zbuforować ich i nie zwiększyć wydajności odczytu obiektu sterty raz zamiast czytać go za każdym razem iteracja.
Właśnie dlatego volatileistnieje słowo kluczowe .
Aby ta klasa pomocnicza była poprawna odrobinę lepiej 1) w środowiskach wielowątkowych, powinna mieć:
public volatile bool isComplete = false;
ale oczywiście, ponieważ jest to kod generowany automatycznie, nie możesz go dodać. Lepszym podejściem byłoby dodanie kilku lock()znaków wokół odczytów i zapisów isCompletedlub użycie innych gotowych do użycia narzędzi do synchronizacji lub obsługi wątków / zadań, zamiast próbować to zrobić na czysto metalowym (co nie będzie czystym metalem, ponieważ jest to C # w CLR z GC, JIT i (..)).
Różnica w trybie debugowania występuje prawdopodobnie dlatego, że w trybie debugowania wykluczonych jest wiele optymalizacji, więc można, no cóż, debugować kod widoczny na ekranie. W związku z tym while (!isComplete)nie jest zoptymalizowany, więc można tam ustawić punkt przerwania, i dlatego isCompletenie jest agresywnie buforowany w rejestrze lub stosie na początku metody i jest odczytywany z obiektu na stercie przy każdej iteracji pętli.
BTW. To tylko moje przypuszczenia. Nawet nie próbowałem tego skompilować.
BTW. To nie wygląda na błąd; bardziej przypomina bardzo niejasny efekt uboczny. Ponadto, jeśli mam rację, może to być brak języka - C # powinien umożliwiać umieszczanie słowa kluczowego „volatile” na zmiennych lokalnych, które są przechwytywane i promowane do pól członkowskich w domknięciach.
1) Zobacz poniżej komentarz Erica Lipperta na temat volatilei / lub ten bardzo interesujący artykuł pokazujący poziomy złożoności związane z zapewnieniem, że kod, na którym volatilesię opieramy, jest bezpieczny ... uh, dobrze ... och, powiedzmy OK.