UDP接收方法使用Task替代Thread(解决关闭程序未响应的问题)

news/2025/3/3 17:06:01/

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();}

http://www.ppmy.cn/news/1576333.html

相关文章

多元数据直观表示(R语言)

一、实验目的&#xff1a; 通过上机试验&#xff0c;掌握R语言实施数据预处理及简单统计分析中的一些基本运算技巧与分析方法&#xff0c;进一步加深对R语言简单统计分析与图形展示的理解。 数据&#xff1a; 链接: https://pan.baidu.com/s/1kMdUWXuGCfZC06lklO5iXA 提取码: …

避坑!用Docker搞定PHP开发环境搭建(Mac、Docker、Nginx、PHP-FPM、XDebug、PHPStorm、VSCode)

本次更新主要是对环境版本进行了更新&#xff0c;例如php 7.3.7升级到了7.3.8&#xff0c;另外之前的版本有同学踩了坑&#xff0c;主要是官方docker镜像php:7.3.7-fpm和php:7.3.8-fpm使用了不同版本的debian&#xff0c;后面会提到&#xff0c;请各位同学留意。 因为最近换电脑…

【vue-echarts】——01.认识echarts

文章目录 前言一、echarts二、使用步骤1.vue cli创建项目并安装第三方模块echarts2.显示图表总结前言 定制的数据可视化图表。ECharts最初由百度团队开源,并于2018年初捐赠给Apache基金会,成为ASF孵化级项目。2021年1月26日晚,Apache基金会官方宣布ECharts项目正式毕业。 一…

动态内存池设计与环形缓冲区实现详解

一、动态内存池设计 在嵌入式系统中&#xff0c;频繁使用 malloc 和 free 会导致内存碎片和性能问题。动态内存池通过预分配固定大小的内存块&#xff0c;并统一管理分配与释放&#xff0c;显著提高内存使用效率和实时性。 静态内存分配&#xff1a;在编译时确定大小&#xf…

实现dify与docker下载安装

1.先要下载安装wsl &#xff0c;先在任务面板进行一些勾选操作&#xff0c;控制面板快捷键ctrlx 2.下载安装wsl 在cmd中输入 wsl --status&#xff0c;如果报错则进行wsl --update&#xff0c;下载过慢则先按ctrlc终止程序&#xff0c;后输入wsl --update -web download 3.下…

LabVIEW 项目长时间稳定运行注意事项

利用 LabVIEW 开发的上位机显示界面通过网络与数字板实现数据通讯&#xff0c;运行一周左右会出现一次数据掉线&#xff08;数据采集不上来&#xff09;&#xff0c;需重新 Connect 才能恢复的问题。 出现这种情况&#xff0c;可能是以下几方面原因导致&#xff1a; 网络通讯方…

加入二极管的NE555 PWM 电路

只用电阻、电容构成的一般定时电路的占空比无法低于50%&#xff0c;如下图&#xff1a; 电容的充电路径上串联了R1 和R2&#xff0c;而放电路径上只有R2&#xff0c;所以放电的时间不可能比充电长。加入二极管就能解决这个问题&#xff0c;用二极管把充电和放电路径分离开&…

问题修复-后端返给前端的时间展示错误

问题现象&#xff1a; 后端给前端返回的时间展示有问题。 需要按照yyyy-MM-dd HH:mm:ss 的形式展示 两种办法&#xff1a; 第一种 在实体类的属性上添加JsonFormat注解 第二种&#xff08;建议使用&#xff09; 扩展mvc框架中的消息转换器 代码&#xff1a; 因为配置类继…