Unity--线程的一些测试

本文详细探讨了Unity中多线程与协程的使用方法及注意事项,包括线程创建、运行、停止及资源释放的过程。通过具体代码示例,分析了不同线程控制方式的优缺点,强调了合理管理线程资源的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unity----事件类型----1 -> https://blog.youkuaiyun.com/MikuLingSSS/article/details/82357104

================================================================================================

做一个实验,代码如下

2018-10-30:我犯了一个致命的错误,我原先以为 yield return 0.1f; 是等待0.1s之后执行,后来测试发现 waitForSource 才是等待一定时间之后再次执行,下面的代码就不修改了,

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System;

public class TimeRunningMethod: MonoBehaviour  
{
    Thread testThread;
    void Start()
    {
        testThread = new Thread(() =>
        {
            try
            {
                Debug.Log("System.Threading->Start");
                while (true)
                {
                    Debug.Log("System.Threaing->Running!!!!!!!!!!");
                    Thread.Sleep(100);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError("Thread Error Logs->" + ex.Message);
            }
        });
        testThread.Start();
        StartCoroutine(test());
        IsInvokeStart = true;
        InvokeRepeating("printf", 0, 0.1f);  // 这个方法,应该是类似于Timer.Interval
    }

    void Update()
    {

        if (Input.GetKeyDown(KeyCode.A))
        {
            StopCoroutine(test());
            Debug.LogWarning("Ienumerator->Stop");
        }

        if (Input.GetKeyDown(KeyCode.S))
        {
            CancelInvoke("printf");
            Debug.LogWarning("InvokeRepeating->Stop");
        }

        if (Input.GetKeyDown(KeyCode.D))
        {
            testThread.Abort();
            testThread = null;
            Debug.LogWarning("System.Threading->Stop");
            Debug.Log(testThread == null ? "11" : "00"); // 输出11
        }

    }

    private bool IsInvokeStart;
    void printf()
    {
        if (IsInvokeStart)
        {
            IsInvokeStart = false;
            Debug.LogWarning("InvokeRepeating->Start");
        }
        Debug.Log("InvokeRepeating->hello world!!!!!!!!!!");
    }

    IEnumerator test()
    {
        Debug.LogWarning("Ienumerator->Start");
        while (true)
        {
            Debug.Log("Ienumerator->hello world!!!!!!!!!!");
            yield return 0.1f;
        }
    }

    private void OnDestroy()
    {
        //if (testThread != null)
        //{
            //testThread.Abort();
        //}
    }

}

直接退出:

可以看到,如果不是正常退出的话,由Unity主线程创建的子线程是不会随着main线程退出而退出的,这时候就需要我们自己手动去释放线程资源,解决方案,吧OnDestroy里面的代码块取消注释,这也是我前两篇Socket里面最后在OnDestroy里面写销毁代码的原因,

注意,如果你使用了多线程,那么最好写一个特殊的单例来存储所有的关于线程的创建和释放的方法,原因大家应该都清楚,就是场景切换之后的脚本问题。。。

====================================使用Update里面的按键结束=======================================

        注意:线程的Abort,如我上一篇中所说,其实是让线程抛出一个异常,所以catch里面的代码块会一并执行

         可以看到,协程的Stop并没有起到我们想要的效果,说一下我的想法,当我们执行Stop的时候,他其实就应该Return掉,但是他内部的条件一直为true,所以造成了一种无限goto的现象,所以根本走不到Return这个条件,而这种情况,我们可以使用特殊的变量来结束

修改一下代码,新加一个Manager的类

class Manager
{
    public static bool IsStopIenumerator = false;
}
//////////////////////TimeRunningMethod修改这些
void Update()
{
     try // 关于协程的退出改成这样
     {
          if (Input.GetKeyDown(KeyCode.A))
          {
              StopCoroutine(test());
              Debug.LogWarning("Ienumerator->Stop");
              Manager.IsStopIenumerator = true;
          }
      }
      catch (Exception ex)
      {
          Debug.LogError("Stop Ienumerator->" + ex.Message);
      }
      // ....... 下面的代码都没变
}

IEnumerator test() // 协程
{
    Debug.LogWarning("Ienumerator->Start");
    while (true)
    {
        if (Manager.IsStopIenumerator)
            throw new Exception("Ienumerator Stop");
        Debug.Log("Ienumerator->hello world!!!!!!!!!!");
        yield return 0.1f;
    }
}

运行结果:

       

================================================================================================

                                                                           第二个测试,单例的释放时间

================================================================================================

 

using System.Collections.Generic;
using UnityEngine;
using System;
public class InstanceTestS : MonoBehaviour
{

    void Start()
    {
        Test.Instance.t.Add("1");
    }

}

class Test : IDisposable
{
    private static Test instance;
    public static Test Instance
    {
        get
        {
            instance = instance == null ? new Test() : instance;
            return instance;
        }
    }

    public List<int> t;
    private Test()
    {
        t = new List<int>();
    }

    ~Test()
    {
        t = null;
        Debug.LogError("Exit");
        if (t == null)
        {
            Debug.LogError("t == null -> Exit");
        }
    }

}

执行结果:

可以看到,在Unity主线程退出之后,单例的内存并没有被释放,或者说是他的内存释放并没有被我所捕捉到,而是延缓到了我下一次开辟内存时,我尝试在OnDestroy中使用GC.collect() 然而并没有什么用处,

       可能会有人说,这些其实并没有什么影响,就和win的system idle process进程一样,但是,如果不知道这些的话。。。

修改代码:

/// 上面的代码没有修改
class Test
{
    private static Test instance;
    public static Test Instance
    {
        get
        {
            instance = instance == null ? new Test() : instance;
            return instance;
        }
    }

    public List<int> t;
    private Thread thread;
    private Test()
    {
        thread = new Thread(Superposition);
        t = new List<int>();
        thread.Start();
    }

    private void Superposition()
    {
        while (true)
        {
            if (t == null)
            {
                thread.Abort();
            }
            for (int i = 0; i < t.Count; i++)
            {
                t[i] += 1;
            }
            Debug.LogError("Thread Running!");
            Thread.Sleep(100); // 这句话至关重要
        }
    }

    ~Test()
    {
        t = null;
        Debug.LogError("Exit");
        if (t == null)
        {
            Debug.LogError("t == null -> Exit");
        }
    }
}

因为析构函数并没有被进入,所以进程不会被终结,

再来看上面的代码块,当把Sleep去掉之时,这个代码只能执行一次,或者说关机一次执行一次,下一次就会陷入程序假死状态,我不太确定问题的原因,但是猜测是当我们再次执行的时候,因为上一次的执行没有检测到退出,所以新的线程和原先的线程发生了一个死锁,致使主线程无限期的等待在构造函数之中,目前可以想到的方法是静态判断,或者继承自IDis,使用dispose在OnDestroy中手动调用Instance的清理方法,但是这样如果我们使用的线程过多,就会出现非常麻烦的情况。。。

所以暂时还是以静态Manager来进行终结和内存的释放,这样一来,我们只需要在OnDestroy中将某个静态参数进行修改,后续的线程检测这个参数出现改变时,就会调用~Ctor()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值