当前位置: 首页 > article >正文

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

http://www.kler.cn/a/568935.html

相关文章:

  • Flink事件时间和处理时间咋区分
  • yolov8_pose模型,使用rknn在安卓RK3568上使用
  • 深入解析 MySQL 中的时间函数:NOW() 与 SYSDATE() 的奥秘
  • TCP的四次挥⼿为什么是四次?为什么不能是三 次
  • 【计算机网络——概述】
  • 深搜专题7:最大质数
  • 【基于Raft的KV共识算法】-序:Raft概述
  • JavaEE基础之- 过滤器和监听器Filter and Listener
  • Deepseek 模型蒸馏
  • 每日OJ_牛客_NC316体育课测验(二)_拓扑排序_C++_Java
  • FPGA开发,使用Deepseek V3还是R1(3):系统级与RTL级
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(8)
  • Ubuntu 20.04 安装 Node.js 20.x、npm、cnpm 和 pnpm 完整指南
  • LangPrompt提示词
  • 基于单片机的GPS定位系统设计
  • ETF期权的结算价如何结算?
  • 深度解析Ant Design Pro 6开发实践
  • 【MySQL】(2) 库的操作
  • 基于STM32的智能家居中控系统
  • 【定昌Linux系统】部署了java程序,设置开启启动