问题描述:
在做服务器连接测试的时候,为了检测消息接收和回复,使用了如下代码来定时检测信息。
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)只能作为最后一个方法执行。这个还困扰了我几分钟。搞得东西多了,在简单的东西上反而有点迷。
以上。