【Unity】线程关闭方法

1 前言

        本文将介绍Unity中的线程关闭方法,并分析常见关闭方法中存在的问题。

2 常见线程关闭方法

        程序中常常有一些包含无限循环的线程存在,所以也需要有对应的关闭方法。线程的关闭方式通常有三种方法:

  1. 将线程设置为后台线程,主线程结束时后台线线程自动关闭。(无法使用)
  2. 调用Abort()函数终止线程。(不推荐)
  3. 使用bool变量控制线程结束循环,进而结束。(推荐)

上面已经标识了是否推荐,后面将对这三种方法进行说明。

2.1 后台线程

什么是后台线程?

        线程分为前台线程和后台线程,当我们使用new Thread创建一个线程后,其默认为前台线程,之后通过设置线程参数IsBackground = true,可将线程设置为后台线程。
        在进程关闭时,若前台线程没有结束,进程就无法结束,进程必须等待前台线程执行完毕再结束。当前台线程执行完毕,进程结束,此时若有后台线程未结束,则会直接终止后台线程。 
        但是在Unity中,这种方法并不适用。当我们创建一个执行无限循环的后台线程,之后再关闭进程,会发现线程无法终止。

代码:

using System.Threading;
using UnityEngine;

public class N_002_ThreadTest : MonoBehaviour
{
    //定义线程
    private Thread th1;

    private void Start()
    {
        //创建线程
        th1 = new Thread(test1);
        //设置为后台线程
        th1.IsBackground = true;
        //开启线程
        th1.Start();
    }

    /// <summary>
    /// th1线程处理函数
    /// </summary>
    void test1()
    {
        //持续打印输出
        while (true)
        {
            Debug.Log("Thead Log!");
        }
    }
}

演示:

可以发现在关闭运行后,线程仍然存活,在控制台持续输出内容。因此,该方法不可用。

        PS:当然这是在编辑器中的情况,打包后运行,然后退出程序会不会终止后台线程,我并不确定。各位感兴趣的话可以去测试下,但无论如何靠结束程序来终止线程的方法都不太提倡,既然是我们自己开的线程,就要由我们自己关闭。

2.2 Abort函数

        用于终止线程的函数,可通过线程自身调用。        

2.2.1 Abort函数的问题

        调用线程的Abort函数可以直接终止线程,多好的函数,然而!调用Abort函数并不一定会终止线程,也可能会抛出异常(概率抽奖)。

代码:

using System;
using System.Threading;
using UnityEngine;

public class N_002_ThreadTest : MonoBehaviour
{
    //定义线程
    private Thread th1;

    private void Start()
    {
        //创建线程
        th1 = new Thread(test1);
        //设置为后台线程
        th1.IsBackground = true;
        //开启线程
        th1.Start();
    }

    /// <summary>
    /// th1线程处理函数
    /// </summary>
    void test1()
    {
        //持续打印输出
        while (true)
        {
            Debug.Log("Thead Log!");
        }
    }

    //绘制UI
    private void OnGUI()
    {
        //绘制按钮,Abort终止线程th1
        if (GUI.Button(new Rect(120, 10, 80, 20), "Abort th1"))
        {
            th1.Abort();
        }
    }
}

演示(成功):

演示(失败):

PS:

  1. abort不是立刻关闭线程,如果其后紧跟线程状态输出语句,会发现,线程状态还是isAlive,有一个时间间隔,状态才改变。
  2. 在抛出异常后,后续再次执行Abort函数,无效,既不终止线程也不抛出异常。

2.2.2 问题讨论

        根据抛出异常的内容,可推测出“Abort无法终止线程的原因是其与线程内的执行内容发生了冲突”。接下来我们针对这个推测进行一些测试:

1、如果线程处于Sleep状态,是否可以Abort终止?

2、如果while(true)中无内容,是否可以Abort终止?

3、如果是线程自己执行Abort,是否可以Abort终止?

“测试”

1、如果线程处于Sleep状态,是否可以Abort终止?

代码:

using System;
using System.Threading;
using UnityEngine;

public class N_002_ThreadTest : MonoBehaviour
{
    //定义线程
    private Thread th1;

    private void Start()
    {
        //创建线程
        th1 = new Thread(test1);
        //设置为后台线程
        th1.IsBackground = true;
        //开启线程
        th1.Start();
    }

    private void Update()
    {
        //持续输出线程状态
        Debug.Log("th1线程存活状态:" + th1.IsAlive);
    }

    /// <summary>
    /// th1线程处理函数
    /// </summary>
    void test1()
    {
        //开局先睡个10s
        Thread.Sleep(10000);

        //持续打印输出
        while (true)
        {
            Debug.Log("Thead Log!");
        }
    }

    //绘制UI
    private void OnGUI()
    {
        //绘制按钮,Abort终止线程th1
        if (GUI.Button(new Rect(120, 10, 80, 20), "Abort th1"))
        {
            th1.Abort();
        }
    }
}

演示:

可以终止。

2、如果while(true)中无内容,是否可以Abort终止?

代码:

using System;
using System.Threading;
using UnityEngine;

public class N_002_ThreadTest : MonoBehaviour
{
    //定义线程
    private Thread th1;

    private void Start()
    {
        //创建线程
        th1 = new Thread(test1);
        //设置为后台线程
        th1.IsBackground = true;
        //开启线程
        th1.Start();
    }

    private void Update()
    {
        //持续输出线程状态
        Debug.Log("th1线程存活状态:" + th1.IsAlive);
    }

    /// <summary>
    /// th1线程处理函数
    /// </summary>
    void test1()
    {
        //持续循环
        while (true)
        {
            //无
        }
    }

    //绘制UI
    private void OnGUI()
    {
        //绘制按钮,Abort终止线程th1
        if (GUI.Button(new Rect(120, 10, 80, 20), "Abort th1"))
        {
            th1.Abort();
        }
    }
}

演示:

可以终止。

3、如果是线程自己执行Abort,是否可以Abort终止?

代码:

using System;
using System.Threading;
using UnityEngine;

public class N_002_ThreadTest : MonoBehaviour
{
    //定义线程
    private Thread th1;

    //状态控制
    private bool isOver_th1 = false;//th1终止状态

    private void Start()
    {
        //创建线程
        th1 = new Thread(test1);
        //设置为后台线程
        th1.IsBackground = true;
        //开启线程
        th1.Start();
    }

    private void Update()
    {
        //持续输出线程状态
        Debug.Log("th1线程存活状态:" + th1.IsAlive);
    }

    /// <summary>
    /// th1线程处理函数
    /// </summary>
    void test1()
    {
        //持续打印输出
        while (true)
        {
            //若状态改为终止状态,则自行Abort终止
            if (isOver_th1)
            {
                th1.Abort();
            }
            Debug.Log("Thead Log!");
        }
    }

    //绘制UI
    private void OnGUI()
    {
        //绘制按钮,修改线程th1终止状态
        if (GUI.Button(new Rect(120, 10, 80, 20), "Abort th1"))
        {
            isOver_th1 = true;
        }
    }
}

演示:

可以终止。

        PS:对于这种线程自己执行Abort的方法看似是一种解决方案,但我认为是有隐患的,前面也说了Abort不是立刻终止线程的,从执行完Abort函数到终止线程之间是有时间的,而在这段时间,线程会执行Abort函数后的内容,在执行过程中,可能会因为“终止线程”戛然而止,不是很安全。(不只是局限于线程自己调用Abort的情况,只是由此引出,只要调用Abort函数便有这种不安全性。不过若是线程自己调用Abort,那么可以紧接着阻塞线程,等待线程结束,也是个处理方法,但其他方式的Abort调用就不好说了。)

“结论”

由上述可得结论:

1、如果线程处于Sleep状态,是否可以Abort终止?可以。

2、如果while(true)中无内容,是否可以Abort终止?可以。

3、如果是线程自己执行Abort,是否可以Abort终止?可以。

        这三种情况本身都避免了Abort函数与线程本身执行内容的冲突,结果则是都可以使用Abort来终止线程。这证明了我们之前的推测。
        那么“使用Abort函数”这个方法能用吗?当然可以,但是要控制好在合适的时机调用Abort,防止冲突的发生,还要考虑逻辑戛然而止的情况,这自然是比较困难的,所以不推荐。

2.3 Bool变量控制

        使用一个Bool变量来控制线程的结束,这无疑是目前比较好的方法。我们在设计一个线程任务时,应该提前预留好结束线程的方式,并使用一个Bool变量来控制,把线程的结束逻辑掌握在我们自己手中。
        这种方式的好处是简单、好控制,且在结束线程的同时,能够保证线程执行逻辑的完整性,不会出现上文中所提到的“终止线程导致逻辑戛然而止的情况”。
        控制的方式有很多,具体该如何控制要根据线程执行的逻辑来决定。这里举个简单的例子:比如将无限循环的while(true){}中的true修改为一个变量来控制,需要退出时就将其置为false来退出循环。下面演示了这种方式。

代码:

using System;
using System.Threading;
using UnityEngine;

public class N_002_ThreadTest : MonoBehaviour
{
    //定义线程
    private Thread th1;

    //状态控制
    private bool isRunning_t1 = true;//th1运行状态

    private void Start()
    {
        //创建线程
        th1 = new Thread(test1);
        //设置为后台线程
        th1.IsBackground = true;
        //开启线程
        th1.Start();
    }

    private void Update()
    {
        //持续输出线程状态
        Debug.Log("th1线程存活状态:" + th1.IsAlive);
    }

    /// <summary>
    /// th1线程处理函数
    /// </summary>
    void test1()
    {
        //若线程运行状态,则持续打印输出
        while (isRunning_t1)
        {
            Debug.Log("Thead Log!");
        }

        //结束时离开while循环,然后执行
        Debug.Log("线程th1结束了!");
    }

    //绘制UI
    private void OnGUI()
    {
        //绘制按钮,修改线程th1运行状态
        if (GUI.Button(new Rect(120, 10, 80, 20), "End th1"))
        {
            isRunning_t1 = false;
        }
    }
}

演示:

建议使用此方法控制线程结。

3 结束语

        综上所述,Unity线程如何关闭?

  1. 使用Abort函数终止线程。但要承担Abort函数与线程内执行内容冲突,进而导致抛出异常且无法终止线程的风险,且要考虑逻辑执行戛然而止的问题。
  2. 使用Bool变量控制线程结束。设计线程任务之初预留好结束逻辑,使用Bool变量控制线程结束。简单、好控制。(推荐)

猜你喜欢

转载自blog.csdn.net/Davidorzs/article/details/134216164
今日推荐