Мне удалось воспроизвести это поведение только в режиме Release
, и я бы сказал, что, похоже, это проблема компилятора + JIT.
Здесь разборка (можно посмотреть в VS Debug -> Windows -> Разборка) код, сгенерированный для не-l oop:
Console.WriteLine("before loop");
00007FFA92B7223C mov rcx,2284EE030C8h
00007FFA92B72246 mov rcx,qword ptr [rcx]
00007FFA92B72249 call 00007FFA92B707C0
00007FFA92B7224E mov rcx,qword ptr [rbp-28h]
00007FFA92B72252 cmp dword ptr [rcx+0E0h],1
00007FFA92B72259 je 00007FFA92B7227A
{
Console.Write("");
00007FFA92B7225B mov rcx,2284EE03060h
00007FFA92B72265 mov rcx,qword ptr [rcx]
00007FFA92B72268 call 00007FFA92B70868
00007FFA92B7226D mov rcx,qword ptr [rbp-28h]
00007FFA92B72271 cmp dword ptr [rcx+0E0h],1
00007FFA92B72278 jne 00007FFA92B7225B
}
Console.WriteLine("after loop");
А вот код с пустым:
Console.WriteLine("before loop");
00007FFA92B5223C mov rcx,2BA99F630C8h
00007FFA92B52246 mov rcx,qword ptr [rcx]
00007FFA92B52249 call 00007FFA92B507C0
00007FFA92B5224E mov rcx,qword ptr [rbp-28h]
00007FFA92B52252 mov ecx,dword ptr [rcx+0E0h]
00007FFA92B52258 cmp ecx,1
00007FFA92B5225B jne 00007FFA92B52258
{
//Console.Write("");
}
Console.WriteLine("after loop");
См. Инструкцию 00007FFA92B5225B jne 00007FFA92B52258
( jne ) он указывает на инструкцию cmp
, которая после инструкции call
(которая должна вызывать метод получения свойства websocket.State
), и если мы посмотрим на аналогичную инструкцию 00007FFA92B62278 jne 00007FFA92B6225B
в версии с непустым циклом мы увидим, что он указывает на 00007FFA92B6225B mov rcx,243DCA13060h
, что на перед на call
единицу (и поле состояния устанавливается асинхронно, когда сокет готов).
TBH Я не знаю, что делать дальше и кому ты звонишь сейчас. Gostbusters? =)
UPD.
Сыграли немного больше. Не утверждал бы, что я полностью понимаю проблему, но вот минимальное повторение:
public static void Main(string[] args)
{
var c = new Container();
c.SetNumber();
Console.WriteLine("Before");
while (c.NumberProp != 1)
{
//Console.WriteLine("in");
}
Console.WriteLine("Success!");
Console.ReadLine();
}
class Container
{
public void SetNumber()
{
Task.Run(() =>
{
Thread.Sleep(100);
NumberProp = 1;
});
}
public int NumberProp { get; set; }
}
Отправленный вопрос на GitHub
LAST (I надеюсь) UPD
Вы задали два вопроса: «Почему это так? Что я делаю не так?»
Давайте начнем с последнего, как обсуждалось в комментариях, вы должны переместили Send
logi c к вашему открытому обратному вызову следующим образом:
private static void websocket_Opened(object sender, EventArgs e)
{
((WebSocket) sender).Send(....);
}
Что касается первого, как правильно сказано на Github (и я был сонным и глупым во время первоначального анализа и полностью забыл о это) это не ошибка, это «ожидаемое» непредсказуемое поведение, свойство причины (его вспомогательное поле) изменяется в одном потоке и анализируется в другом, и код может подвергаться различным оптимизациям (компилятору, джиттеру и даже во время выполнения Процессор зависит от архитектуры). В моем репродукции проблему можно решить с помощью ключевого слова volitile
, например:
class Container
{
public void SetNumber()
{
Task.Run(() =>
{
Thread.Sleep(100);
number_backing_field = 1;
});
}
private volatile int number_backing_field;
public int NumerProp => number_backing_field;
}
Если / когда вы решите погрузиться глубже в топи c, я рекомендую вам посмотреть это великий разговор Саши Гольдштейна.