UDP接收方法使用Task替代Thread(解决关闭程序未响应的问题)
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();
}