线程与任务

本文介绍了线程与进程的区别,详细阐述了开启线程的三种方式:异步委托、Thread类和线程池,并探讨了线程池的特性。此外,还讲解了线程的相关概念,如线程状态、优先级和结束线程的方法。最后,对比了Task与Thread的差异,说明了Task的层次结构和使用方法。

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

线程

线程与进程的区别:
进程:是计算机中已运行的实体。在面向线程设计的系统(当代多数系统)中,进程本身不是基本运行单位,而是线程的容器。

线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。也是独立调度和分派的基本单位。

一个进程可以由多个线程辅助执行。


开启线程的三种方式

1.通过异步委托开启线程
通过Func委托创建一个委托变量来指向我们需要使用的方法,使用BeginInvoke方法开启线程。

//需要委托的方法
static int Test(int i,string str)
        {
            Console.WriteLine("Test"+i+str);
            return 100;
        }

static void Main(string[] args)
        {
            //通过委托开启线程
            Func<int,string,int> a = Test;
            IAsyncResult可以取得当前线程的状态
            IAsyncResult ar = a.BeginInvoke(100,"lymon",null, null); //开启一个新的线程去执行a所引用的方法
            Console.WriteLine("Main");
            //检测线程是否结束
            while (ar.IsCompleted == false)
            {
                Console.Write(".");
                Thread.Sleep(10); //控制子线程的检测频率
            }
            int res = a.EndInvoke(ar); //取得异步线程的返回值
            //可以认为线程是同时执行的(异步执行)
            Console.WriteLine(res);

结束线程的三种方法

  • 通过while循环检测
    使用isComplete方法用死循环判断线程是否结束,如果结束的话,使用EndInvoke方法取得线程的返回值
while (ar.IsCompleted == false)
            {
                Console.Write(".");
                Thread.Sleep(10); //控制子线程的检测频率
            }
            int res = a.EndInvoke(ar); //取得异步线程的返回值
  • 通过等待句柄检测
    使用AsyncWaitHandle.WaitOne方法判断在规定时间内线程是否结束,将结果返回一个布尔值
//检测线程是否结束的另一种方法
            //1000毫秒是超时时间,如果1000毫秒过后,线程还没结束,则会返回false,否则返回true
            bool isEnd = ar.AsyncWaitHandle.WaitOne(1000); 
            if (isEnd)
            {
                int res = a.EndInvoke(ar);
                Console.WriteLine(res);
            }
  • 通过回调函数检测
    前面开启线程的BeginInvoke方法里,a.BeginInvoke(100,”lymon”,null, null);传递的参数里最后两个null,第一个null参数是委托类型的参数,表示回调函数,当线程结束的时候会调用这个委托指向的方法;第二个null参数是用来给回调函数传递数据的。
static void Main(string[] args)
{
            //通过回调函数检测线程结束
            Func<int, string, int> a = Test;
            //后两个参数的第一个是委托类型的参数,表示回调函数;第二个参数是用来给回调函数传递数据的
            IAsyncResult ar = a.BeginInvoke(100, "zhuang", OnCallBack, a);

            //回调函数改用Lambda表达式
            //通过Lambda表达式取得,最后一个参数不用传递数据
            a.BeginInvoke(100, "zhuang", ar =>
            {
                int res = a.EndInvoke(ar);
                Console.WriteLine(res+"在Lambda表达式中取得");
            }, null);

            Console.ReadKey();
        }

        static void OnCallBack(IAsyncResult ar)
        {
            Func<int, string, int> a = ar.AsyncState as Func<int, string, int>;
            int res = a.EndInvoke(ar);
            Console.WriteLine(res+"在回调函数中取得");
        }
}

2.通过Thread类发起线程

使用Thread类创建Thread对象

//带参数的方法
static void DownLoad(object fileName)
        {
            Console.WriteLine("开始下载"+fileName+Thread.CurrentThread.ManagedThreadId); //获取当前线程的ID
            Thread.Sleep(2000);
            Console.WriteLine("下载完成");
        }
        static void Main(string[] args)
        {
            //常规的发起线程方式
            Thread t = new Thread(DownLoad); //创建Thread对象,线程没有启动
            t.Start(); //线程开始启动

            //Lambda表达式
            Thread t = new Thread(() =>
            {
                Console.WriteLine("开始下载" + Thread.CurrentThread.ManagedThreadId); //获取当前线程的ID
                Thread.Sleep(2000);
                Console.WriteLine("下载完成");
            });
            t.Start();

            //当有参数的时候,想要传递参数需要在开始线程时传递
            Thread t = new Thread(DownLoad);
            t.Start("xxx.txt"); //在开始线程这里传递参数

            Console.ReadKey();
        }

还可以创建一个MyThread类用于定义自己想定义的参数

//自己的MyThread类
class MyThread
    {
        private string fileName;
        private string filePath;

        public MyThread(string fileName,string filePath)
        {
            this.fileName = fileName;
            this.filePath = filePath;
        }

        public void DownLoad()
        {
            Console.WriteLine("开始下载"+filePath+fileName);
            Thread.Sleep(2000);
            Console.WriteLine("下载完成");
        }
    }

static void Main(string[] args)
{
            MyThread mt = new MyThread("abc.txt","http://www.baidu.com");
            Thread t = new Thread(mt.DownLoad);
            t.Start();
}

3.通过线程池开启线程
当我们有很多方法需要同时进行,再一个个创建线程就会很麻烦,我们可以通过线程池中取得线程使用。
C#中有ThreadPool类来管理线程,它的最大线程数是可以配置的,双核CPU中默认线程数为1023个工作线程和1000个IO线程。
线程池的特性:

  • 线程池中的所有线程都是后台线程,不能设置为前台线程
  • 线程池中的线程无法修改线程优先级和名称
  • 线程池中的线程只用于时间较短的任务,如果线程需要一直运行直到程序结束,则应该使用Thread类创建线程。

使用线程池开启线程:

static void ThreadMethod(Object a)
        {
            Console.WriteLine("线程开始"+Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000);
            Console.WriteLine("线程结束");
        }
        static void Main(string[] args)
        {//开启线程的方法
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            ThreadPool.QueueUserWorkItem(ThreadMethod);
            Console.ReadKey();
        }

关于线程的其他概念:

  • 前台线程、后台线程
    Thread类创建的线程默认都是前台线程。
    当前台线程还没结束的时候,程序就不会结束,而当前台线程结束的时候,后台线程无论有没有执行完,程序都会停止并且关闭后台线程。

  • 线程优先级
    线程是由操作系统调度的,当有很多线程需要执行的之后,线程调度器会根据线程优先级去判断先去执行哪一个线程,如果优先级全都相同,则使用循环调度规则逐个执行每个线程。

  • 线程状态
    当我们调用Thread类的Start方法,线程并不是马上进入Running状态,而是处于Unstarted状态,当线程调度器选择了这个线程,它的状态才会修改为Running状态。使用Sleeping则是使线程进入WaitSleepJoin状态。
    我们可以使用Thread对象的Abort方法来停止线程,它会在终止线程中抛出一个ThreadAbortException方法,我们可以trycatch这个异常来处理线程结束后的一些清理工作。
    当我们不确定线程结束的事件要多久的时候,可以使用Join方法,停止当前线程,设置为WaitSleepJoin状态,将目标线程执行完之后,再开始当前线程。

t.Join(); //使当前线程睡眠,等待加入的新线程执行完再继续执行下面的代码

任务

任务的使用方法和Thread是一样的,创建任务有两个方法
1.Task直接创建
2.TaskFactory创建

static void ThreadMethod()
        {
            Console.WriteLine("任务开始");
            Thread.Sleep(2000);
            Console.WriteLine("任务结束");
        }
        static void Main(string[] args)
        {
            //创建方法1
            Task t = new Task(ThreadMethod);
            t.Start();


            //创建方法2
            TaskFactory tf = new TaskFactory();
            //使用StartNew方法之后不需要使用t.Start()
            Task t = tf.StartNew(ThreadMethod);


            Console.WriteLine("Main");
            Console.ReadKey();
        }

Task和Thread区别
Task就像是一个被封装好的Thread类,但是Task是能够保证一定被执行完成的。
当程序中,Task1是依赖Task2(Task2执行完才能执行Task1)的时候可以使用ContinueWith方法。

Task的层次结构
我们在一个任务中启动一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行。如果父任务执行完而子任务没有执行完,父任务的状态设置为WaitingForChildrenToComplete,只有子任务执行完,父任务状态才会变成RunToCompletion。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值