Websocket只有在Console.Write()存在的情况下才会接收消息,这是为什么?

问题描述 投票:0回答:1

我对websocket这个奇怪的 "问题 "真的很头疼,我从10年的停顿中回归编程,我是第一次学习websocket,所以我一定是做错了什么。

我用C#做了一个简单的代码,打开一个websocket连接,并从Binance Websocket API接收消息(图书订单更新),我遵循了他们的指导方针,比如发送JSON格式的订阅消息,以便能够接收消息。

它工作了... 但只有当我把 Console.Write("") 在下面的while循环中,如果我不这样做,它就会一直处于open状态,永远不会退出循环,永远站在那里,命令是 Console.WriteLine("after loop") 永远不会被打印出来。我把这个循环来等待websocket连接到服务器(状态从连接到打开)。

如果你看到下面的代码,随着 Console.Write("") 注释了循环永远不会退出,websocket消息永远不会被接收,取消注释,一切都会正常工作。

我附上了2张图片,显示了在Console中的输出,也试着去掉 "using "语句,并把一个无限循环代替了 Console.ReadKey();. 但似乎什么都没用,只有当我在循环里面取消写命令时,我才觉得很奇怪...

为什么会这样?我到底做错了什么?

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}");
        }
    }
}

这是Console.Write未注释时的输出。

enter image description here

这是有注释的Console.Write的输出。

enter image description here

c# websocket console-application
1个回答
1
投票

我能够重现这种行为,只有在 Release 模式,我想说这似乎是编译器+JIT的问题。

这里的反汇编(可以在VS Debug -> Windows -> Disassembly中查看)是为非循环生成的代码。

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 指令(应该调用getter的 websocket.State 属性),而如果我们看一下类似的指令。00007FFA92B62278 jne 00007FFA92B6225B 在非空循环版本中,我们会看到它指向了 00007FFA92B6225B mov rcx,243DCA13060h 也就是 之前call 一(当socket准备好时,状态字段会异步设置)。

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

最后(我希望) 更新

你问了两个问题。"为什么会这样?我做错了什么"

让我们从后者开始,正如评论中所讨论的那样,你应该把你的名字移到 Send 逻辑到你的开放回调,就像这样。

    private static void websocket_Opened(object sender, EventArgs e)
    {
        ((WebSocket) sender).Send(....);
    }

至于前者,正如Github上的正确声明(我在最初的分析中犯困和愚蠢,完全忘记了它),这不是一个bug,这是 "预期的 "不可预测的行为,因为属性(它的支持字段)在一个线程中改变,在另一个线程中分析,代码可能会受到不同的优化(编译器,抖动,甚至在运行时由CPU取决于架构)。在我的repo中,这个问题可以通过以下方法解决 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;
    }

当你决定深入研究这个话题时,我建议你看一下。这个 Sasha Goldshtein的精彩演讲。

© www.soinside.com 2019 - 2024. All rights reserved.