关于timer.Elapsed导致执行顺序异常问题

问题描述:

在做服务器连接测试的时候,为了检测消息接收和回复,使用了如下代码来定时检测信息。

			var timer = new System.Timers.Timer(1000.0 / 20);         
            timer.Elapsed += (object sender, ElapsedEventArgs e) =>
            {
    
       
                if (_socket.Connected)
                {
    
    
                    _socket.Tick(1);
                }                        
            };
            timer.AutoReset = true;
            timer.Enabled = true;
public int Tick(int processLimit, Func<bool> checkEnabled = null)
        {
    
    
            // only if state was created yet (after connect())
            // note: 我们不会选中“仅在已连接的情况下”,因为我们也想在以后继续处理“断开连接”消息!
            if (state == null)
                return 0;

            // process up to 'processLimit' messages
            for (int i = 0; i < processLimit; ++i)
            {
    
    
                // 如果启用了“镜像”场景消息,则启用此检查
                if (checkEnabled != null && !checkEnabled())
                    break;

                // 先偷看。 允许我们处理第一个排队的条目,同时通过不删除任何内容来保持池中的byte []有效。
                if (state.receivePipe.TryPeek(out int _, out EventType eventType, out ArraySegment<byte> message))
                {
    
    

                    switch (eventType)
                    {
    
    
                        case EventType.Connected:
                            OnConnected?.Invoke();
                            break;
                        case EventType.Data:
                            OnData?.Invoke(message);
                            break;
                        case EventType.Disconnected:
                            OnDisconnected?.Invoke();
                            break;
                    }
                    //重要说明:现在,在完成处理事件之后,将队列从队列中取出并返回到池中。
                    state.receivePipe.TryDequeue();
                }
                // no more messages. stop the loop.
                else break;
            }
            // 返回下一次要处理的内容
            return state.receivePipe.TotalCount;
        }

然后就出现了,我tick内的一些代码逻辑混乱,理论上是从上至下依次执行的。但实际情况是执行了多次 OnData?.Invoke(message); 之后才进行了state.receivePipe.TryDequeue();移除队列的操作。

解决方案

可能是不太熟悉这些东西,经过多次测试仍不清楚原因。
从表现上来看就是我在移除队列之前执行了多次操作。但如果按照顺序执行的逻辑。
第一次执行完 OnData?.Invoke(message);
信息就会被移除state.receivePipe.TryDequeue();
在下一次进入该方法检测时就不会被检测到了state.receivePipe.TryPeek(out int _, out EventType eventType, out ArraySegment<byte> message)

所以进行如下尝试修复

    if (state.receivePipe.TryPeek(out int _, out EventType eventType, out ArraySegment<byte> message))
                {
    
    
                    //重要说明:现在,在完成处理事件之后,将队列从队列中取出并返回到池中。
                    state.receivePipe.TryDequeue();
                    switch (eventType)
                    {
    
    
                        case EventType.Connected:
                            OnConnected?.Invoke();
                            break;
                        case EventType.Data:
                            OnData?.Invoke(message);
                            break;
                        case EventType.Disconnected:
                            OnDisconnected?.Invoke();
                            break;
                    }
                }

这样确实解决了我提出的问题。

但因为这是使用别人的插件包,而且有很多人再用并没有人提出以上问题,所以还是觉得不改动别人的源码以避免其他问题出现。

然后就尝试了看看这个timer.Elapsed的描述。
在这里插入图片描述
然后红框部分是我觉得可能导致这种问题的原因。
这里就关系到了线程,当然作为Unity开发,基本是已经远离了这个概念,因为Unity只有主线程。
所以我觉得把这个属性设置一下应该就好了,但案例只有一个Form类作为它的对象,经过多番尝试不知道如何操作。(这块有大佬有对应的使用方法可以评论一下。)

然后我就使用了另外一种方法
如下

 			var timer = new System.Timers.Timer(1000.0 / 20);        
            timer.Elapsed += (object sender, ElapsedEventArgs e) =>
            {
    
    
                lock (this)
                {
    
    
                    if (_socket.Connected)
                    {
    
    
                        _socket.Tick(1);
                    }
                }                                   
            };
            timer.AutoReset = true;
            timer.Enabled = true;

在执行检测的时候lock一下避免 事件的处理 Elapsed 持续时间超过 Interval ,则可能会在另一个线程上再次引发该事件 ThreadPool 。
这样也可以解决问题。

关于一些特殊的尝试记录

其实在以上解决方案中间,我还尝试了另外一种方案。即都在同一个线程操作。然后循环执行的不使用 timer.Elapsed而使用while(true)。
测试结果当然是没有问题,但问题在于在同一个线程的话,while(true)只能作为最后一个方法执行。这个还困扰了我几分钟。搞得东西多了,在简单的东西上反而有点迷。

以上。

猜你喜欢

转载自blog.csdn.net/qq_39860954/article/details/117226193