UDP接收方法使用Task替代Thread(解决关闭程序未响应的问题)
- 1 前言
- 1.1 问题原因及解决方案
- 1.2 Unity主线程被阻塞的原因和解决办法
- 1.3 在Unity中,Task 和 Thread的区别
- 1.4 在WinForm和Unity中使用Thread和Task的区别
- 2 代码示例
- 2.1 Thread 接收方法和释放方法
- 2.2 Task 接收方法和释放方法
1 前言
UDP对象正常注册收发方法,Unity发布后,点击关闭按钮程序不响应,需要强制结束程序,不存在任何报错问题。
1.1 问题原因及解决方案
Unity主线程被阻塞,导致程序在点击关闭按钮后无法正常响应。
… 这种情况通常发生在主线程被长时间运行的操作阻塞时,比如在UDP通信中等待数据接收。通过使用Task来在单独的线程中接收UDP数据,可以避免主线程被阻塞的情况,从而使程序在点击关闭按钮后可以正常响应并释放资源。
…
… 使用Task来处理UDP数据接收可以确保这个操作在一个独立的线程中进行,不会影响主线程的正常运行。当需要关闭程序时,主线程可以顺利地处理关闭逻辑并释放资源,而不会被阻塞在UDP数据接收的操作上。
1.2 Unity主线程被阻塞的原因和解决办法
在Unity中,主线程负责处理游戏逻辑、渲染等工作。当一个Thread作为接收线程时,可能会导致阻塞主线程的原因有几点:
… 1 主线程和接收线程之间的通信:接收线程需要通过某种方式将接收到的数据传递给主线程进行处理。如果通信机制不够高效或存在问题,可能会导致接收线程在传递数据时阻塞主线程。
…
… 2 线程调度问题:Unity的主线程和其他线程之间存在线程调度机制。如果接收线程的优先级较高或占用了过多的资源,可能会导致主线程被接收线程“抢占”,从而导致主线程无法及时处理关闭事件。
…
… 3 线程同步问题:如未正确处理线程间的同步和互斥操作,可能导致接收线程和主线程之间出现竞争条件或死锁,从而导致主线程无法响应关闭事件。
解决这个问题的方法通常是优化线程间通信、合理设置线程优先级、使用线程同步机制等。确保线程之间的协作顺畅,避免阻塞主线程的情况发生。
1.3 在Unity中,Task 和 Thread的区别
… Task是.NET Framework中的一种高级异步编程模型,能够更轻松地处理异步操作和任务并发。Task是建立在线程池上的,它可以自动分配线程并在任务完成后自动回收线程资源。Task可以方便地处理异步操作的结果和异常,并且支持丰富的任务组合和链式操作。
… Thread是操作系统中最基本的执行单元,是一条执行路径。在Unity中直接使用Thread会涉及到线程管理和同步的复杂性,不太适合处理异步操作。由于Unity是单线程的游戏引擎,直接在Unity主线程中创建新的线程可能会导致不可预测的问题。因此,通常不建议在Unity中直接使用Thread。
… 在Unity中,推荐使用Task来处理异步操作和任务并发,利用Task的高级特性能够更加方便地进行异步编程。
1.4 在WinForm和Unity中使用Thread和Task的区别
… WinForm和Unity都是基于.NET Framework的,因此它们都支持.NET的多线程编程模型,包括Thread和Task。
… 在WinForm中使用Thread时,需要自行管理线程的创建、启动、同步等操作,需要手动处理线程间的通信和线程安全性。而在Unity中,由于Unity是一个游戏引擎,它具有自己的多线程管理机制,因此在Unity中使用Thread时需要特别注意与Unity主线程的交互,以避免可能出现的线程安全问题。
… Task是.NET Framework中用于异步编程的一种高级抽象,它提供了更便利的异步操作管理和线程调度功能。在WinForm和Unity中都可以使用Task来执行异步操作,Task相比于Thread更加易于使用和管理。
… 在Unity中,由于其游戏循环的特性,通常建议使用Unity提供的协程(Coroutine)来处理异步任务,而不是直接使用Thread。协程可以在Unity主线程中运行,与游戏循环更加协调,更适合处理Unity引擎相关的逻辑和操作。
2 代码示例
2.1 Thread 接收方法和释放方法
private UdpClient HostUdpClient;private Thread UdpthrRecv;//接收线程try{UdpthrRecv = new Thread(UdpReceiveThd);UdpthrRecv.IsBackground = true;UdpthrRecv.Start();}catch (Exception ex){return ("[Err-UdpthrRecv Start] " + ex.Message + "\r\n");}/// <summary>/// UDP接收数据/// </summary>private void UdpReceiveThd(){byte[] bytRecv;IPEndPoint RecvIpep = new IPEndPoint(ip, port);while (true){try{if (EnlableRecv){bytRecv = HostUdpClient.Receive(ref RecvIpep);ByteHandle(bytRecv); // 处理接收数据}else{Thread.Sleep(100);}}catch (Exception ex){// ("[Err-UdpReceiveThd] " + ex.Message);}}}private bool udpdisposed = false;protected virtual void Dispose(bool disposing){if (!udpdisposed){if (disposing){try { UdpthrRecv.Abort(); }catch { }try { HostUdpClient.Close(); }catch { }}udpdisposed = true;}}public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}
2.2 Task 接收方法和释放方法
private UdpClient HostUdpClient;private Task receiveTask;private CancellationTokenSource cts = new CancellationTokenSource();
private volatile bool IsStarted = false;try{HostUdpClient = new UdpClient(LocalPointSet);HostUdpClient.Client.IOControl(unchecked((int)SIO_UDP_CONNRESET), new byte[] { Convert.ToByte(false) }, null);Debug.Log("启动成功!");}catch (Exception ex){Debug.Log("启动异常! " + ex.Message);}try{receiveTask = Task.Run(ReceiveMassage, cts.Token);}catch (Exception ex){Debug.Log("接收线程启动异常! " + ex.Message);}byte[] bytes;public async Task ReceiveMassage(){while (!cts.Token.IsCancellationRequested){try{if (IsStarted && HostUdpClient != null){UdpReceiveResult result = await HostUdpClient.ReceiveAsync();bytes = result.Buffer;ByteHandle(bytes); // 处理接收数据}}catch (OperationCanceledException){// 预期关闭行为Debug.Log("接收任务 - 取消成功");break;}catch (ObjectDisposedException){// 预期关闭行为Debug.Log("接收任务 - 对象已释放");break;}catch (Exception ex){Debug.Log("[接收数据 - 异常!] " + ex.Message);}}}///<summary>/// 程序关闭///</summary>public void Closing(){IsStarted = false;if (receiveTask != null){cts.Cancel(); // 线程取消try{receiveTask.Wait(1000); // 等待1秒Debug.Log("接收任务 - 停止成功");}catch (AggregateException ex){Debug.Log("接收任务 - 停止异常: " + ex.Message);}}else{Debug.Log("接收线程 - 未启动");}if (HostUdpClient != null){try { HostUdpClient.Close(); Debug.Log("资源释放成功"); }catch { Debug.Log("资源释放异常"); }}else{Debug.Log("未启动");}}/// <summary>/// Unity 退出事件处理/// </summary>private void OnApplicationQuit(){Closing();}