多线程整理

一、简介

       1.1、进程

               当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。 一个进程是由多个线程组成

       1.2、线程

               线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。

       1.3、句柄

               句柄是Windows系统中对象或实例的标识。这些对象包括模块、应用程序实例、窗口、控制、位图、GDI对象、资源、文件等。

       1.4、多线程

                1.4.1、概念

程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

多线程一定是运行在多核计算机上。用CPU运行空间换取时间,CPU是分片执行的。单核CPU上谈多线程,都是扯淡。

1.4.2、优点

提高CPU利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。(牺牲空间计算资源,来换取时间)

1.4.3、缺点

    1. 占用内存多。线程也是程序,所以线程运行需要占用计算机资源,线程越多占用资源也越多。
    2. 占用CPU多。多线程需要协调和管理,所以需要CPU跟踪线程,消耗CPU资源。
    3. 多线程存在资源共享问题。线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。
    4. 管理麻烦,容易产生bug。线程太多会导致控制太复杂,最终可能造成很多Bug。

 1.4.4、业务场景

    1.  主线程试图执行冗长的耗时操作,导致系统界面卡顿,客户体验较差。可以新开线程处理冗长的耗时操作。
    2. 请求别的数据库服务,业务服务等。新开一个线程,让主线程继续干别的事。
    3. 利用多线程拆分复杂运算,提高计算速度。

        1.4.5、不建议使用多线程的业务场景

                       当单线程能很好处理问题,就不要使用多线程。 

1.5、同步/异步

        1.5.1、同步方法

 线性执行,从上往下依次执行,同步方法执行慢,消耗的计算机资源少。

1.5.2、异步方法

线程和线程之间,不再线型执行,多个线程总的耗时少,执行快,消耗的计算机资源多,各线程执行是无序的。

二、C#中的多线程

          2.1、Thread

                   最早的多线程处理方式,.NET 1.0时代,逐渐被微软抛弃,微软强推Task。

                   2.1.1、前台线程/后台线程

                               前台线程:界面关闭,线程随之消失。

                               后台线程:界面关闭,线程继续执行,完毕后才结束。

                  2.2、其他关键字

                           线程优先级、数据槽、内存栅栏

          2.2、  ThreadPool

                     线程池:不需要程序员对线程的数量管控,提高性能,防止滥用,去掉了很多在Thread中没有必要的Api。

          2.3、Task    

Task出现之前,微软的多线程处理方式有:Thread→ThreadPool→委托的异步调用。.NET 4.0,在ThreadPool的基础上进行的封装,Task的控制和扩展性很强,在线程的延续、阻塞、取消、超时等方面远胜于Thread和ThreadPool。

         2.3.1、开启线程方式

1)、new Task().Start() 

                       2)、Task.Run() 

 3)、Task.Factory.StartNew() 

                     4)、new Task().RunSynchronously()(同步方式,上面三种异步方式) 

          2.3.2、线程等待

1、task.Wait()

等待task内部执行完毕,才会往后直行,卡主线程,Task实例方法。

task.Wait(1000);//等待1000毫秒后就往后执行,不管有没有执行结束。

task.Wait(TimeSpan.FromMilliseconds(1000));//等待1000毫秒后就往后执行,不管有没有执行结束。

               2、WaitAny

               某一个任务执行结束后,去触发一个动作,卡主线程,Task静态方法。数据有可能是来自于第三方接口,缓存,数据库,查询的时候,我们不确定,开启几个线程同时查询,只要一个返回了就返回界面。

                    3、WaitAll

所有任务执行完成后,去触发一个动作,卡主线程,Task静态方法
数据是来自于第三方接口,缓存,数据库,查询的时候,开启几个线程同时查询,等所有数据全部查询出来,一起返回界面

                4、WhenAny

与下面ContinueWith配合执行,当传入的线程中任何一个线程执行完毕,继续执行ContinueWith中的任务(属于开启新线程,不卡主线程),Task静态方法。

               5、WhenAll

 当其中所有线程执行完成后,新开启了一个线程执行,继续执行新业务,所以执行过程中,不卡主线程,Task静态方法。 

List<Task> taskList = new List<Task>();
TaskFactory factory = new TaskFactory();
taskList.Add(factory.StartNew(() => { Debug.WriteLine("查询数据库"); }));
taskList.Add(factory.StartNew(() => { Debug.WriteLine("查询缓存"); }));
taskList.Add(factory.StartNew(() => { Debug.WriteLine("查询接口"); }));
Task.WhenAll(taskList.ToArray()).ContinueWith((n) => { Debug.WriteLine($"查到数据,返回界面!"); });
View Code

              6、ContinueWhenAny

 某一个任务执行结束后,去触发一个动作,不卡主线程,TaskFactory实例方法,等价于WhenAny+ContinueWith

List<Task> taskList = new List<Task>();
TaskFactory factory = new TaskFactory();
taskList.Add(factory.StartNew(obj => Coding("张三", "数据库设计"), "张三"));
taskList.Add(factory.StartNew(obj => Coding("李四", "接口对接"), "李四"));
taskList.Add(factory.StartNew(obj => Coding("王五", "Webapi"), "王五"));
taskList.Add(factory.StartNew(obj => Coding("赵六", "前端页面"), "赵六"));
factory.ContinueWhenAny(taskList.ToArray(), ts =>
{
    Debug.WriteLine($"{ts.AsyncState}同学开发完毕,田七开始测试!");
});
View Code

              7、ContinueWhenAll

 所有任务执行完成后,去触发一个动作,不卡主线程,TaskFactory实例方法,等价于WhenAll+ContinueWith

List<Task> taskList = new List<Task>();
TaskFactory factory = new TaskFactory();
taskList.Add(factory.StartNew(obj => Coding("张三", "数据库设计"), "张三"));
taskList.Add(factory.StartNew(obj => Coding("李四", "接口对接"), "李四"));
taskList.Add(factory.StartNew(obj => Coding("王五", "Webapi"), "王五"));
taskList.Add(factory.StartNew(obj => Coding("赵六", "前端页面"), "赵六"));
factory.ContinueWhenAll(taskList.ToArray(), ts =>
{
    Debug.WriteLine($"所有人开发完毕,我们一起庆祝一下吃个饭!");
});
View Code

       2.3.3、TaskCreationOptions枚举类详解

 一个Task内部,可以开启线程,Task内部的线程可以理解为子线程,Task为父线程,创建Task实例的时候可以传入TaskCreationOptions枚举参数来影响线程的运行方式

1、None,默认情况

 父线程不会等待子线程执行结束才结束。

Task task = new Task(() =>
{
    Debug.WriteLine($"task--start--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}");
    Task task1 = new Task(() =>
    {
        this.DoSomething("task1");
    });
    Task task2 = new Task(() =>
    {
        this.DoSomething("task2");
    });
    task1.Start();
    task2.Start();
    Debug.WriteLine($"task--end--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}");
});
task.Start();
task.Wait();
View Code

               2、AttachedToParent

                子线程附加到父线程,父线程必须等待所有子线程执行结束才能结束,相当于Task.WaitAll(task1, task2)。 

Task task = new Task(() =>
{
    Debug.WriteLine($"task--start--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}");
    Task task1 = new Task(() =>
    {
        this.DoSomething("task1");
    }, TaskCreationOptions.AttachedToParent);
    Task task2 = new Task(() =>
    {
        this.DoSomething("task2");
    }, TaskCreationOptions.AttachedToParent);
    task1.Start();
    task2.Start();
    Debug.WriteLine($"task--end--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}");
});
task.Start();
task.Wait();
View Code

              3、DenyChildAttach

 不允许子任务附加到父任务上,反AttachedToParent,和默认效果一样 

Task task = new Task(() =>
{
    Debug.WriteLine($"task--start--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}");
    Task task1 = new Task(() =>
    {
        this.DoSomething("task1");
    }, TaskCreationOptions.AttachedToParent);
    Task task2 = new Task(() =>
    {
        this.DoSomething("task2");
    }, TaskCreationOptions.AttachedToParent);
    task1.Start();
    task2.Start();
    Debug.WriteLine($"task--end--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}");
}, TaskCreationOptions.DenyChildAttach);
task.Start();
task.Wait();
View Code

             4、PreferFairness

相对来说比较公平执行的先申请的线程优先执行

Task task1 = new Task(() =>
{
    this.DoSomething("task1");
}, TaskCreationOptions.PreferFairness);
Task task2 = new Task(() =>
{
    this.DoSomething("task2");
}, TaskCreationOptions.PreferFairness);
task1.Start();
task2.Start();
View Code

            5、LongRunning

 事先知道是长时间执行的线程就加这个参数,线程调度会优化

   6、RunContinuationsAsynchronously

         强制以异步方式执行添加到当前任务的延续。

   7、HideScheduler

防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default当前计划程序。

  8、TaskContinuationOptions枚举类详解

        ContinueWith可以传入TaskContinuationOptions枚举类参数来影响线程的运行方式。

        (1)、None,默认情况

          任务顺序执行

Task task1 = new Task(() =>
{
    this.DoSomething("task1");
});

Task task2 = task1.ContinueWith(t =>
  {
      this.DoSomething("task2");
  });

Task task3=task2.ContinueWith(t =>
{
    this.DoSomething("task3");
});

task1.Start();
View Code

               (2)、LazyCancellation

         取消该线程,该线程的前一个线程和后一个线程顺序执行。

CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();

Task task1 = new Task(() =>
{
    this.DoSomething("task1");
});

Task task2 = task1.ContinueWith(t =>
{
    this.DoSomething("task2");
}, source.Token,TaskContinuationOptions.LazyCancellation, TaskScheduler.Current);

Task task3 = task2.ContinueWith(t =>
{
    this.DoSomething("task3");
});

task1.Start();
View Code

               (3)、ExecuteSynchronously

         前后任务由同一个线程执行

Task task1 = new Task(() =>
{
    this.DoSomething("task1");
});

Task task2 = task1.ContinueWith(t =>
{
    this.DoSomething("task2");
},TaskContinuationOptions.ExecuteSynchronously);

task1.Start();
View Code

               (4)、NotOnRanToCompletion

Task task1 = new Task(() =>
{
    this.DoSomething("task1");
    //异常了,表示未执行完成,task2能执行
    //不异常,表示执行完成,task2不能执行
    throw new Exception("手动制造异常,表示不能执行完毕");
});

Task task2 = task1.ContinueWith(t =>
{
    this.DoSomething("task2");
}, TaskContinuationOptions.NotOnRanToCompletion);

task1.Start();
View Code

               (5)、OnlyOnRanToCompletion

                    延续任务必须在前面task完成状态才能执行,和NotOnRanToCompletion正好相反

Task task1 = new Task(() =>
{
    this.DoSomething("task1");
    //异常了,表示未执行完成,task2不能执行
    //不异常,表示执行完成,task2能执行
    throw new Exception("手动制造异常,表示不能执行完毕");
});

Task task2 = task1.ContinueWith(t =>
{
    this.DoSomething("task2");
}, TaskContinuationOptions.OnlyOnRanToCompletion);

task1.Start();
View Code

               (6)、NotOnFaulted

                  延续任务必须在前面task完成状态才能执行,效果和OnlyOnRanToCompletion差不多。

Task task1 = new Task(() =>
{
    this.DoSomething("task1");
    //throw new Exception("手动制造异常");
});

Task task2 = task1.ContinueWith(t =>
{
    this.DoSomething("task2");
}, TaskContinuationOptions.NotOnFaulted);

task1.Start();
View Code

                (7)、OnlyOnFaulted

                延续任务必须在前面task未完成状态才能执行,效果和NotOnRanToCompletion差不多。

Task task1 = new Task(() =>
{
    this.DoSomething("task1");
    //throw new Exception("手动制造异常");
});

Task task2 = task1.ContinueWith(t =>
{
    this.DoSomething("task2");
}, TaskContinuationOptions.OnlyOnFaulted);

task1.Start();
View Code

                (8)、OnlyOnCanceled

                  前面的任务未被取消才执行后面的任务。

CancellationTokenSource cts = new CancellationTokenSource();
Task task1 = new Task(() =>
{
    this.DoSomething("task1");
    cts.Cancel();
});

Task task2 = task1.ContinueWith(t =>
{
    this.DoSomething("task2");
}, TaskContinuationOptions.OnlyOnCanceled);

task1.Start();
View Code

                (9)、NotOnCanceled

                   前面的任务被取消才执行后面的任务

CancellationTokenSource cts = new CancellationTokenSource();
Task task1 = new Task(() =>
{
    this.DoSomething("task1");
    cts.Cancel();
});

Task task2 = task1.ContinueWith(t =>
{
    this.DoSomething("task2");
}, TaskContinuationOptions.NotOnCanceled);

task1.Start();
View Code

                (10)、PreferFairness

System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。

                (11)、LongRunning

指定某个任务将是运行时间长、粗粒度的操作。 它会向 System.Threading.Tasks.TaskScheduler 提示,过度订阅可能是合理的。

                 (12)、AttachedToParent

                                指定将任务附加到任务层次结构中的某个父级。

                  (13)、DenyChildAttach

                                如果尝试附有子任务到创建的任务,指定 System.InvalidOperationException 将被引发。

                  (14)、HideScheduler

防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default当前计划程序。

         9、延迟执行

 Task.Delay(),一般和ContinueWith配合使用,执行的动作就是ContinueWith内部的委托,委托的执行有可能是一个全新的线程,也有可能是主线程。

//开启线程后,线程等待3000毫秒后执行动作,不卡主线程
Task.Delay(3000).ContinueWith(t =>
{
    this.DoSomething("张三");
});
View Code

     2.3.4、Task进阶

                1、多线程捕获异常

                        1)、线程不等待,捕捉不到异常

多线程中,如果发生异常,使用try-catch包裹,捕捉不到异常,异常还没发生,主线程已经执行结束。

//捕捉不到异常
try
{
    Task task = Task.Run(() =>
    {
        int i = 0;
        int j = 10;
        int k = j / i; //尝试除以0,会异常
    });
}
catch (AggregateException aex)
{
    foreach (var exception in aex.InnerExceptions)
    {
        Debug.WriteLine($"线程不等待:异常{exception.Message}");
    }
}
View Code

                        2)、线程不等待,线程内部捕获异常

多线程中,如果要捕捉异常,可以在线程内部try-catch,可以捕捉到异常。

//捕捉到异常
try
{
    Task task = Task.Run(() =>
    {
        try
        {
            int i = 0;
            int j = 10;
            int k = j / i; //尝试除以0,会异常
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"线程内异常{ex.Message}");
        }
    });
}
catch (AggregateException aex)
{
    foreach (var exception in aex.InnerExceptions)
    {
        Debug.WriteLine($"线程不等待:异常{exception.Message}");
    }
}
View Code

                       运行结果

线程内异常Attempted to divide by zero.

                      3)、线程等待,能够捕获异常

        • 多线程中,如果要捕捉异常,需要设置主线程等待子线程执行结束,可以捕捉到异常

        • 多线程内部发生异常后,抛出的异常类型是system.AggregateException

//捕捉到异常
try
{
Task task = Task.Run(() =>
{
    int i = 0;
    int j = 10;
    int k = j / i; //尝试除以0,会异常
});
//线程等待
task.Wait();
}
catch (AggregateException aex)
{
foreach (var exception in aex.InnerExceptions)
{
    Debug.WriteLine($"线程等待:异常{exception.Message}");
}
}
View Code

                    运行结果:

线程等待:异常Attempted to divide by zero.

         2、线程取消

            线程取消是不能从外部取消的,线程取消的实质还是通过变量去控制程序的运行和结束,正常结束,或者发生异常结束

 

其他参考:链接

 

posted on 2024-04-15 17:00  木乃伊人  阅读(10)  评论(0编辑  收藏  举报

导航