C# 多线程不同开启方式

一、线程不同的开启方式

线程是没有父子关系的,只有前台和后台的区别。相反 Task 任务就存在父子关系,并且子任务会影响父任务的状态

       1.通过委托开启线程

        委托类型下有一个BeginInvoke方法,和EndInvoke方法,BeginInvoke方法是将委托指向的方法通过线程调用,BeginInvoke的返回值是位于System命名空间下的接口 IAsyncResult,通过IAsyncResult我们可以知道线程的执行情况。

EndInvoke方法是将有返回值方法的返回值返回,返回值类型是委托定义的返回值类型.

       

 

        无返回值的:

    System.Action<string> threads;
    void Start()
    {
        threads = ThreadMove;
        threads.BeginInvoke("开启了线程", null, null);
        Debug.Log("我是主进程里的Debug");
    }
    private void ThreadMove(string value)
    {
        Debug.Log(value);
        Debug.Log("我是线程里的Debug");
    }

        有返回值的:

          1.等待子线程执行完毕,主线程继续,一般这么做程序会卡住,等待子线程执行完毕)

 System.Func<string,int> threads;
    void Start()
    {
        threads = ThreadMove;
        //主线程开始执行
        Debug.Log("主线程开始执行");

        //BeginInvoke的返回值是位于System命名空间下的接口 IAsyncResult
        System.IAsyncResult result = threads.BeginInvoke("开启了线程", null, null);

        //每隔10毫秒检测线程是否执行完毕,IsCompleted属性:当线程执行完毕,返回True
        //while (!result.IsCompleted)
        //{
        //    Thread.Sleep(10);//休息10毫秒检测一次
        //    Debug.Log("线程还没执行完毕,可真慢");
        //}

        //该方法与while循环检测一样,等待线程执行完毕再往下执行,WaitOne中可以传参,例如传入100毫秒,则该方法等待线程100毫秒,超过时间则线程超时,返回false
        //bool isDown = result.AsyncWaitHandle.WaitOne(100);

        //当线程执行完毕,通过EndInvoke接收执行的结果。
        //if (isDown)
        //{
        //    int lengh = threads.EndInvoke(result);
        //    Debug.Log("我是主进程里的Debug,我等待子线程执行完毕我才执行,子线程的结果是:" + lengh);
        //}

        //或者不需要使用while循环或者WaitOne方法,直接调用EndInvoke,同样会等待线程执行完毕
        int lengh = threads.EndInvoke(result);
        Debug.Log("我是主进程里的Debug,我等待子线程执行完毕我才执行,子线程的结果是:" + lengh);
    }
    private int ThreadMove(string value)
    {
        Debug.Log(value+":我先休息一会再返回吧");
        Thread.Sleep(10000);//线程休息5秒后,返回
        return value.Length;
    }

        2.不去等待子线程结束,子线程调用完成的时候回调,主进程也不会卡住,通过向BeginInvoke传递回调方法来实现

    System.Func<string, int> threads;
    void Start()
    {
        threads = ThreadMove;
        //主线程开始执行
        Debug.Log("主线程开始执行");

        //ThreadEnd是线程执行完毕后要回调的方法,threads是给该方法传递的参数Object类型
        threads.BeginInvoke("开启了线程", ThreadEnd, threads);

      
        Debug.Log("我是主进程里的Debug,我也执行了");
    }

    /// <summary>
    /// 线程所执行的方法
    /// </summary>
    private int ThreadMove(string value)
    {
        Debug.Log(value + ":我先休息一会再返回吧");
        Thread.Sleep(10000);//线程休息5秒后,返回
        return value.Length;
    }

    /// <summary>
    /// 线程执行完毕,回调该方法
    /// </summary>
    /// <param name="result">如果要BeginInvoke中回调该方法,该方法必须有一个参数IAsyncResult,用来接收返回结果</param>
    private void ThreadEnd(System.IAsyncResult result)
    {
        //我们通过BeginInvoke传递的threads参数怎么获取呢,result.AsyncState当中储存了我们传入的参数,我们对其进行强制转换即可
        System.Func<string, int> res = (System.Func<string, int>)result.AsyncState;

        //然后再通过EndInvoke方法,获取线程执行结束后的结果
        Debug.Log("线程执行结束:"+res.EndInvoke(result));
    }

     2.通过Thread类开启线程

       

using System;
using System.Threading;
using UnityEngine;

public class LearningType : MonoBehaviour
{
    void Start()
    {
        //通过Therad创建一个线程, 如果线程执行的方法是有参数的,那方法的参数必须是Object类型
        //通过Thread线程开启方法一
        Thread thread = new Thread(ThreadMove);
        thread.Start("我是要传入线程的数据");
        Debug.Log("我是主进程里的Debug,我也执行了");

        //通过Thread线程开启方法二
        ExecuteClass executeClass = new ExecuteClass("我是数据");
        Thread thread1 = new Thread(executeClass.executeMethod);
        thread1.Start();
        Debug.Log("我是主进程里的Debug,我也执行了");
    }
    /// <summary>
    /// 线程所执行的方法
    /// </summary>
    private void ThreadMove(object value)
    {
        Debug.Log("开始执行线程");
        Thread.Sleep(2000);//线程休息2秒
        Debug.Log(value + "成功传入数据");
    }
}

public class ExecuteClass
{
    public string Data;
    public ExecuteClass(string data)
    {
        Data = data;
    }

    public void executeMethod()
    {
        Debug.Log("开始执行线程,数据为:"+Data);
        Thread.Sleep(2000);
        Debug.Log("线程执行完毕");
    }
}

3.前台线程和后台线程

后台线程执行过程中,如果所有的前台线程均执行完毕或者被关掉,那么所有的后台线程也会被关闭

以上未设置IsBackGround的所有线程均属于前台线程

设置后台线程的方法:

using System;
using System.Threading;
using UnityEngine;

public class LearningType : MonoBehaviour
{

    void Start()
    {
        //通过Therad创建一个线程, 如果线程执行的方法是有参数的,那方法的参数必须是Object类型
        //通过Thread线程开启方法一
        Thread thread = new Thread(ThreadMove);
        thread.IsBackground = true;//设置后台线程的方法
        thread.Start("我是要传入线程的数据");
        Debug.Log("我是主进程里的Debug,我也执行了");
    }
    /// <summary>
    /// 线程所执行的方法
    /// </summary>
    private void ThreadMove(object value)
    {
        Debug.Log("开始执行线程");
        Thread.Sleep(2000);//线程休息2秒
        Debug.Log(value + "成功传入数据");
    }
}


4获取线程的状态以及控制线程

using System;
using System.Threading;
using UnityEngine;

public class LearningType : MonoBehaviour
{
    Thread thread;
    ThreadState threadState
    {
        get
        {
            return thread.ThreadState;
        }
    }
    void Start()
    {
        thread = new Thread(Test);

        thread.Start();//开启一个线程
       // thread.Abort();//终止这个线程
       // thread.Join();// 让当前线程睡眠,等待这个线程结束再执行下边的代码

        Debug.Log(thread.ThreadState);
        /*
         *线程的状态是可以多种并存的
        ThreadState.Running;          //当前线程处于运行状态
        ThreadState.Unstarted;        //当前线程处于停止状态
        ThreadState.WaitSleepJoin;  //当我们的线程中调用了Thread.Sleep(1000)的时候,线程进入该状态
        ThreadState.Background;     //当我们将thread.IsbackGrund=true时,线程状态加上Background
        ThreadState.Stopped;          //当前线程执行完毕
        */
    }
    void Test()
    {
        Debug.Log("线程开始了,当前的状态为:" + threadState);
        Thread.Sleep(10000);
        Debug.Log("线程执行完毕,当前的状态为:" + threadState);
    }
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.Log("检测当前线程状态:" + threadState);
        }
        if (Input.GetKeyDown(KeyCode.M))
        {
            thread.IsBackground = true;
            Debug.Log("检测当前线程状态:" + threadState);
        }
    }
}


5.线程池

using System;
using System.Threading;
using UnityEngine;

/// <summary>
/// 通过线程池发起线程
/// 线程池发起的线程比较适合处理一些小型的任务
/// </summary>
public class LearningType : MonoBehaviour
{
    void Start()
    {
        ThreadPool.QueueUserWorkItem(Execute);
        ThreadPool.QueueUserWorkItem(Execute);
    }
   public void Execute(object state)
    {
        Debug.Log("开始执行线程,ID:"+Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(5000);
        Debug.Log("线程执行完毕"+ Thread.CurrentThread.ManagedThreadId);
    }
}

6.以任务的方式开启线程

一下代码中介绍了任务模式、任务工厂模式、以及任务连续模式的使用方法;如果在任务中开启子任务,父任务执行完毕,子任务尚未执行完毕的话,父任务就会等待子任务执行完毕并且把自己的状态设定为WaitingForChildrenToComplete,等到子任务执行完毕的时候,父任务的状态会变成RunToCompletion.

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

/// <summary>
/// 通过线程池发起线程
/// 线程池发起的线程比较适合处理一些小型的任务
/// </summary>
public class LearningType : MonoBehaviour
{
    void Start()
    {
        //任务开启线程方式一:任务模式
        Task task1 = new Task(ExecuteOne);//传递一个需要线程去执行的方法
        task1.Start();

        //任务开启线程方式二:任务工厂
        TaskFactory taskFactory = new TaskFactory();
        Task task2 = taskFactory.StartNew(ExecuteOne);

        //任务的连续功能
        //例如:我们开启了一个任务
        Task task3 = new Task(ExecuteOne);
        task3.Start();
        //然后我们的下一个任务需要在task3任务完成的时候再执行,也就是说task4是依赖task3执行的
        Task task4 = task3.ContinueWith(ExecuteTwo);

        
    }
    public void ExecuteOne()
    {
        Debug.Log("开始执行线程");
        Thread.Sleep(5000);
        Debug.Log("线程执行完毕");
    }
    public void ExecuteTwo(Task t)
    {
        Debug.Log("开始执行线程  "+t.Id+"  "+Task.CurrentId);
        Task task = new Task(ExecuteOne);//在任务中开启一个子任务,父任务会等待子任务执行完毕
        task.Start();
        Thread.Sleep(5000);
        Debug.Log("线程执行完毕");
    }
}


7.多线程运行时可能出现的问题

1.多线程争用问题,多线程调用同一对象并且要修改该对象的成员变量的时候,一定要添加 lock 关键字将对象锁定,其余线程会等待锁定使用完毕并解锁后,再进行使用。

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class LearningType : MonoBehaviour
{
    private int num = 5;
    void Start()
    {
        Multithreading multithreading = new Multithreading();
        //当我们开启一个线程的时候,一下判断不会发生错误
        //Thread thread = new Thread(ExecuteOne);
        //thread.Start(multithreading);
        当我们开启两个线程的时候,就会进入num == 2的判断,导致程序发生错误
        //Thread thread1 = new Thread(ExecuteOne);
        //thread1.Start(multithreading);
        
        
        //这个时候我们需要使用Lock关键字,将对象锁定,等待自己执行完毕之后,在解锁供别的线程使用
        Thread thread3 = new Thread(ExecuteOneLock);
        thread3.Start(multithreading);
        //当我们开启两个线程的时候,就会进入num == 2的判断,导致程序发生错误
        Thread thread4 = new Thread(ExecuteOneLock);
        thread4.Start(multithreading);
    }
    /// <summary>
    /// 没有添加锁定的线程执行发放
    /// </summary>
    public void ExecuteOne(object temp)
    {
        Multithreading multithreading = temp as Multithreading;
        while (true)
        {
            multithreading.ChangeNum();
        }
    }
    /// <summary>
    /// 添加了锁定的线程执行方法
    /// </summary>
    public void ExecuteOneLock(object temp)
    {
        Multithreading multithreading = temp as Multithreading;
        lock (multithreading)  //向系统申请锁定对象,如果multithreading没有被锁定,即可锁定并执行下一步,
                                      //如果multithreading被锁定了,那么程序会等待multithreading解锁再进行锁定,执行下一步
        {
            while (true)
            {
                multithreading.ChangeNum();
            }
        }
    }
}
public class Multithreading
{
    public int num = 2;
    public void ChangeNum()
    {
        num++;
        if (num == 2)
        {
            Debug.Log("发生了多线程同时操作同一变量导致判断错误的问题");
        }
        num = 2;
    }
}


2.多线程的死锁问题

例如:线程一在执行过程中,首先锁定A对象,其次锁定B对象;线程二首先锁定B对象,其次锁定A对象。

死锁的问题就发生在,线程一锁定了A,与此同时线程二锁定了B,此时线程一在等待锁定B,而线程一在等待锁定A,这样谁也等不上谁,程序就卡死在这里了,所以在锁定的时候,一定要考虑清楚锁的先后顺序,尽量避免死锁的问题发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值