Editor卡住的原因
Unity中使用Socket或者多线程,资源没有正确的释放导致Editor卡住。我们的项目中卡住是由于线程没有正确的关掉,TcpClient没有Close导致的。
避免Socket阻塞
在connect的时候如果连不上会等到Timeout,默认Timeout时间是20s。如果放在主循环中连,就会卡住20s。放在协程中会卡住编辑器。也就是说同步连接的时候是一定会等到Timeout的,程序或者Editor必定会卡20s。所以首先要避免连接的时候长时间阻塞。
所以最佳架构是:使用多线程和异步连接。并且消息的收发都使用队列,每个队列开个线程。发送之前检查是否连接上。
在发送队列中,使用TcpClient的异步连接:BeginConnect,然后AsyncWaitHandle.WaitOne来控制timeout。注意不管有没有连接成功,最后都要调用TcpClient.Close()关掉连接。
IAsyncResult result = client.BeginConnect(IPAddress.Parse(host), port, null, null);
connected = result.AsyncWaitHandle.WaitOne(1000, false);
if (connected )
{
client.EndConnect(result);
} else
{
client.Close();
}
线程要正确的退出
thread.abort()一般是无法退出线程的(abort调用了,如果thread.isAlive仍然为True就说明没有退出)。所以通常在线程的主循环中自定义控制变量isStop,需要结束的时候将isStop设置为true,退出主循环。并且需要将线程设置为守护线程(C#的isBackground,后台线程),这样主程序退出,就会自动关闭线程。
thread:
while !isStop
dowork
全部代码如下:
public class NetWork : MonoBehaviour {
public static NetWork netWork = null;
public string host = "127.0.0.1";
public int port = 10002;
private TcpClient client;
private bool __connected = false;
public bool connected
{
get { return __connected; }
}
private NetworkStream stream;
private Thread recvProcess = null, sendProcess = null;
//public InputField hostText, portText;
private volatile bool stopSendProcess, stopRecvProcess;
//发送队列
private Queue<BaseStruct> sendQueue;
//半包缓冲区
private string recvData;
private int maxBufferSize = 4096;
private Queue<BaseStruct> recvQueue;
private void Awake()
{
//保证全局唯一
if (netWork != null)
{
DestroyImmediate(gameObject);
} else
{
DontDestroyOnLoad(gameObject);
netWork = this;
}
sendQueue = new Queue<BaseStruct>();
recvQueue = new Queue<BaseStruct>();
}
private void Start()
{
netWork = this;
sendProcess = new Thread(new ThreadStart(SendProcess));
sendProcess.IsBackground = true;
sendProcess.Start();
stopSendProcess = false;
stopRecvProcess = false;
}
public void TryConnect()
{
CreateConnection(host, port);
}
void CreateConnection(string host, int port)
{
try
{
client = new TcpClient();
client.NoDelay = true;
//client.Connect(IPAddress.Parse(host), port);
IAsyncResult result = client.BeginConnect(IPAddress.Parse(host), port, null, null);
__connected = result.AsyncWaitHandle.WaitOne(1000, false);
if (__connected)
{
client.EndConnect(result);
} else
{
client.Close();
}
}
catch (SocketException ex)
{
__connected = false;
Debug.Log("connect error: " + ex.Message);
client.Close();
return;
}
if (__connected)
{
stream = client.GetStream();
if (recvProcess == null)
{
recvProcess = new Thread(new ThreadStart(RecvProcess));
recvProcess.IsBackground = true;
recvProcess.Start();
}
}
}
public static void WriteMessage(BaseStruct item)
{
if (netWork != null)
netWork.Write(item);
}
private void Write(BaseStruct item)
{
sendQueue.Enqueue(item);
}
//数据发送线程,
void SendProcess()
{
while (!stopSendProcess)
{
if (!__connected)
{
TryConnect();
}
while (sendQueue.Count > 0 && __connected)
{
BaseStruct item = sendQueue.Dequeue();
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(item.Serialize());
stream.Write(buffer, 0, buffer.Length);
stream.Flush();
}
Thread.Sleep(5);
}
}
private void RecvProcess()
{
byte[] recvBuf = new byte[maxBufferSize];
while (!stopRecvProcess)
{
int bytesRead = stream.Read(recvBuf, 0, maxBufferSize);
// 解析消息加到 recvQueue
}
//Debug.Log("stopRecvProcess");
}
void OnApplicationQuit()
{
stopSendProcess = true;
stopRecvProcess = true;
if (__connected)
{
__connected = false;
stream.Close();
client.Close();
}
if (recvProcess != null)
{
//recvProcess.Abort();
// 如果没有正确关闭线程,这里的Join就会阻塞,就会卡死编辑器
// recvProcess.Join();
Debug.Log("recvProcess: " + recvProcess.IsAlive);
}
if (sendProcess != null)
{
//sendProcess.Abort();
// sendProcess.Join();
Debug.Log("sendProcess: " + sendProcess.IsAlive);
}
}
}