Websocket только получает сообщения, если Console.Write () присутствует, почему это? - PullRequest
1 голос
/ 03 мая 2020

У меня действительно болит голова из-за этой странной «проблемы» с веб-сокетами, я возвращаюсь к программированию с 10-летней паузой и впервые изучаю веб-сокеты, поэтому я, должно быть, что-то делаю не так.

Я сделал простой код в C#, чтобы открыть подключение к веб-сокету и получать сообщения (обновления порядка книг) из Binance Websocket API, я следовал их указаниям, таким как отправка сообщения подписки в формате JSON, чтобы иметь возможность получать сообщений.

И это работает ... НО только если я поставлю Console.Write("") в то время как l oop ниже, если я не сделаю это, просто остается в ОТКРЫТОМ состоянии и никогда не выходит из l oop, стоя там вечно, команда Console.WriteLine("after loop") никогда не печатается. Я положил это l oop, чтобы дождаться соединения веб-сокета с сервером (состояние от подключения к открытию).

Если вы видите код ниже, с комментарием Console.Write("") l oop никогда не будет выход и сообщения websocket никогда не принимаются, раскомментируйте его, и все будет работать нормально.

Я приложил 2 изображения, показывающих вывод в консоли, также попытался убрать выражение «using» и поставить бесконечный l oop вместо Console.ReadKey();. Но, похоже, ничего не работает, только если я раскомментирую команду записи внутри l oop, то, что я думаю, это странно ...

Почему это так? Что я делаю не так?

using SuperSocket.ClientEngine;
using System;
using WebSocket4Net;

namespace Tests
{
    public class Program
    {
        public static void Main(string[] args)
        {
            using (WebSocket websocket = new WebSocket("wss://stream.binance.com:9443/ws/LOOMBTC@depth@100ms"))
            {
                websocket.Opened += new EventHandler(websocket_Opened);
                websocket.Error += new EventHandler<ErrorEventArgs>(websocket_Error);
                websocket.Closed += new EventHandler(websocket_Closed);
                websocket.MessageReceived += new EventHandler<MessageReceivedEventArgs>(websocket_MessageReceived);
                websocket.Open();

                Console.WriteLine("before loop");
                while (websocket.State != WebSocketState.Open)
                {
                    //Console.Write("");
                }
                Console.WriteLine("after loop");
                websocket.Send("{\"method\": \"SUBSCRIBE\",\"params\":[\"loombtc@depth\"],\"id\": 1}");
                Console.ReadKey();
            }
        }

        private static void websocket_Opened(object sender, EventArgs e)
        {
            Console.WriteLine($"socket OPENED, sender: {sender} and eventargs e: {e}");
        }

        private static void websocket_Error(object sender, ErrorEventArgs e)
        {
            Console.WriteLine($"socket ERROR, sender: {sender} and eventargs e: {e.Exception}");
        }

        private static void websocket_Closed(object sender, EventArgs e)
        {
            Console.WriteLine($"socket CLOSED, sender: {sender} and eventargs e: {e}");
        }

        private static void websocket_MessageReceived(object sender, MessageReceivedEventArgs e)
        {
            Console.WriteLine($"socket MESSAGE RECEIVED, sender: {sender} and eventargs e: {e.Message}");
        }
    }
}

Это вывод с незакомментированной консолью. Запись:

enter image description here

Это вывод с прокомментированной консолью. Написание:

enter image description here

1 Ответ

1 голос
/ 03 мая 2020

Мне удалось воспроизвести это поведение только в режиме 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, я рекомендую вам посмотреть это великий разговор Саши Гольдштейна.

...