从0自学C#11–多线程创建方法汇总以及优缺点

基本概念

1. 进程

进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法直接访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。

2. 应用程序域

使用.NET建立的可执行程序 *.exe,并没有直接承载到进程当中,而是承载到应用程序域(AppDomain)当中。应用程序域是.NET引入的一个新概念,它比进程所占用的资源要少,可以被看作是一个轻量级的进程。

在一个进程中可以包含多个应用程序域,一个应用程序域可以装载一个可执行程序(.exe)或者多个程序集(.dll)。这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。

当一个程序集同时被多个应用程序域调用时,会出现两种情况: 
第一种情况:CLR分别为不同的应用程序域加载此程序集。 
第二种情况:CLR把此程序集加载到所有的应用程序域之外,并实现程序集共享,此情况比较特殊,被称作为Domain Neutral。

3. 线程

线程(Thread)是进程中的基本执行单元,是程序执行流的最小单元。在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。

线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。

4. 三者的关系

进程、应用程序域、线程的关系如下图,一个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于一个应用程序域内。

788.jpg

创建方法

1. 下面用火车票系统,介绍创建多线程的方法。

首先新建控制台程序项目,创建火车票类和人类:Ticket和Person.

class Ticket{    private int count =100;    public int Count    {        get        {            return this.count;        }    }    public string GetTicket()    {        //while (true)        //{            this.count++;            Thread.Sleep(50);            this.count--;        //}        return "G" + this.count--;    }}class Person{    private string name, id;    private int age;    public string Name    {        get        {            return this.name;        }        set        {            if (value.Length > 0 && value.Length  0)            {                this.age = value;            }            else            {                throw new IndexOutOfRangeException("Age must be more than 0.");            }        }    }    public string ID//身份证    {        get        {            return this.id;        }        set        {            if (value.Length == 18)            {                this.id = value;            }            else            {                throw new IndexOutOfRangeException("Lengh of ID must be 16.");            }        }    }    public Person(string nameOfPerson, int ageOfPerson, string idOfPerson)    {        this.name = nameOfPerson;        this.age = ageOfPerson;        this.id = idOfPerson;    }}

其次在Program类中,创建公用的静态方法,后面创建多线程的方法会调用到。

class Program{    static void BuyTicket(object state)    {        Ticket newTic = (Ticket)state;        BuyTicket(newTic);                }    static string BuyTicket(Ticket newTic)    {        lock (newTic)        {            ThreadMessage("Async Thread start:");            Console.WriteLine("Async thread do work!");            string message = newTic.GetTicket();            Console.WriteLine(message + "n");            return message;        }    }    static void ThreadMessage(string data)    {        string message = string.Format("{0}nCurrentThreadId is {1}", data, Thread.CurrentThread.ManagedThreadId);        Console.WriteLine(message);    } }

2. 通过Thread类创建

它能创建并控制线程,设置其优先级并获取其状态。通过ThreadStart来创建一个新线程是最直接的方法,这里不做介绍。ParameterizedThreadStart委托与ThreadStart委托非常相似,但ParameterizedThreadStart委托是面向带参数方法的。注意ParameterizedThreadStart 对应方法的参数为object,此参数可以为一个值对象,也可以为一个自定义对象。

这里介绍通过ParameterizedThreadStart创建。

①代码

static void Main(string[] args){    Ticket tic = new Ticket();    Person[] person = new Person[10]        {            new Person("Nicholas", 21, "000000000000000000"),            new Person("Nate", 38, "111111111111111111"),            new Person("Vincent", 21, "222222222222222222"),            new Person("Niki", 51, "333333333333333333"),            new Person("Gary", 28, "444444444444444444"),            new Person("Charles", 49, "555555555555555555"),            new Person("Karl ", 55, "666666666666666666"),            new Person("Katharine", 19, "777777777777777777"),            new Person("Lee", 25, "888888888888888888"),            new Person("Ann", 34, "99999999999999999"),        };    ThreadMessage("MainThread start");    Console.WriteLine();    Thread[] t = new Thread[person.Length];    for (int i = 0; i < person.Length; i++)    {        t[i] = new Thread(new ParameterizedThreadStart(BuyTicket));        t[i].Start(tic);                       }    for (int i = 0; i < 3; i++)    {        Console.WriteLine("Main thread do work!");        Thread.Sleep(200);    }    Console.ReadKey();}

②运行结果

MainThread startCurrentThreadId is 8Async Thread start:CurrentThreadId is 9Async thread do work!Main thread do work!G100Async Thread start:CurrentThreadId is 10Async thread do work!G99Async Thread start:CurrentThreadId is 11Async thread do work!G98Async Thread start:CurrentThreadId is 12Async thread do work!G97Async Thread start:CurrentThreadId is 13Async thread do work!Main thread do work!G96Async Thread start:CurrentThreadId is 14Async thread do work!G95Async Thread start:CurrentThreadId is 15Async thread do work!G94Async Thread start:CurrentThreadId is 16Async thread do work!G93Async Thread start:CurrentThreadId is 17Async thread do work!Main thread do work!G92Async Thread start:CurrentThreadId is 18Async thread do work!G91

③结论

一共创建了11个线程。

使用ThreadStart与ParameterizedThreadStart建立新线程非常简单,但通过此方法建立的线程难于管理,若建立过多的线程反而会影响系统的性能。当启动一个线程时,会有几百毫秒的时间花费在准备一些额外的资源上,例如一个新的私有局部变量栈这样的事情。每个线程会占用(默认情况下)1MB 内存。有见及此,.NET引入CLR线程池这个概念。

3. 使用线程池

线程池(thread pool)可以通过共享与回收线程来减轻这些开销,允许多线程应用在很小的粒度上而没有性能损失。CLR线程池并不会在CLR初始化的时候立刻建立线程,而是在应用程序要创建线程来执行任务时,线程池才初始化一个线程。线程的初始化与其他的线程一样。

在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。

CLR线程池分为工作者线程(workerThreads)与I/O线程 (completionPortThreads) 两种,工作者线程是主要用作管理CLR内部对象的运作,I/O(Input/Output) 线程顾名思义是用于与外部系统交换信息。

3.1 通过QueueUserWorkItem使用线程池

①代码

static void Main(string[] args){    Ticket tic = new Ticket();    Person[] person = new Person[10]        {            new Person("Nicholas", 21, "000000000000000000"),            new Person("Nate", 38, "111111111111111111"),            new Person("Vincent", 21, "222222222222222222"),            new Person("Niki", 51, "333333333333333333"),            new Person("Gary", 28, "444444444444444444"),            new Person("Charles", 49, "555555555555555555"),            new Person("Karl ", 55, "666666666666666666"),            new Person("Katharine", 19, "777777777777777777"),            new Person("Lee", 25, "888888888888888888"),            new Person("Ann", 34, "99999999999999999"),        };    ThreadPool.SetMaxThreads(1000, 1000);    ThreadPool.SetMinThreads(2, 2);    ThreadMessage("MainThread start");    Console.WriteLine();    foreach(Person someone in person)    {        ThreadPool.QueueUserWorkItem(new WaitCallback(BuyTicket), tic);    }    for (int i = 0; i < 3; i++)    {        Console.WriteLine("Main thread do work!");        Thread.Sleep(200);    }    Console.ReadKey();}

②运行结果

MainThread startCurrentThreadId is 8Main thread do work!Async Thread start:CurrentThreadId is 11Async thread do work!G100Async Thread start:CurrentThreadId is 10Async thread do work!G99Async Thread start:CurrentThreadId is 9Async thread do work!G98Async Thread start:CurrentThreadId is 12Async thread do work!Main thread do work!G97Async Thread start:CurrentThreadId is 11Async thread do work!G96Async Thread start:CurrentThreadId is 10Async thread do work!G95Async Thread start:CurrentThreadId is 9Async thread do work!G94Async Thread start:CurrentThreadId is 12Async thread do work!Main thread do work!G93Async Thread start:CurrentThreadId is 11Async thread do work!G92Async Thread start:CurrentThreadId is 10Async thread do work!G91

③结论

一共创建了5个线程,小于Thread类的10。

通过ThreadPool.QueueUserWorkItem启动工作者线程虽然是方便,但WaitCallback委托指向的必须是一个带有Object参数的无返回值方法,这无疑是一种限制。若方法需要有返回值,或者带有多个参数,这将多费周折。有见及此,.NET提供了另一种方式去建立工作者线程,那就是委托。

3.2 通过委托使用线程池

①代码

delegate string MyDelegate(Ticket tic);//可以带多个参数static void Main(string[] args){    Ticket tic = new Ticket();    Person[] person = new Person[10]        {                    new Person("Nicholas", 21, "000000000000000000"),            new Person("Nate", 38, "111111111111111111"),            new Person("Vincent", 21, "222222222222222222"),            new Person("Niki", 51, "333333333333333333"),            new Person("Gary", 28, "444444444444444444"),            new Person("Charles", 49, "555555555555555555"),            new Person("Karl ", 55, "666666666666666666"),            new Person("Katharine", 19, "777777777777777777"),            new Person("Lee", 25, "888888888888888888"),            new Person("Ann", 34, "99999999999999999"),        };    ThreadPool.SetMaxThreads(1000, 1000);    ThreadPool.SetMinThreads(2, 2);    ThreadMessage("MainThread start");    Console.WriteLine();    foreach (Person someone in person)    {        MyDelegate myDelegate = new MyDelegate(BuyTicket);        myDelegate.BeginInvoke(tic, new AsyncCallback(Completed), someone);    }    for (int i = 0; i < 3; i++)    {        Console.WriteLine("Main thread do work!");        Thread.Sleep(200);    }    Console.ReadKey();}static void Completed(IAsyncResult result){    Console.WriteLine();    ThreadMessage("Async Completed");    //获取委托对象,调用EndInvoke方法获取运行结果    AsyncResult _result = (AsyncResult)result;    MyDelegate myDelegate = (MyDelegate)_result.AsyncDelegate;    string data = myDelegate.EndInvoke(_result);    //获取Person对象    Person person = (Person)result.AsyncState;    Console.WriteLine("Person name is " + person.Name);    Console.WriteLine("Person age is " + person.Age);    Console.WriteLine("Person ID is " + person.ID);    Console.WriteLine("Tick ID is "+ data);    Console.WriteLine();}

②运行结果

MainThread startCurrentThreadId is 8Main thread do work!Async Thread start:CurrentThreadId is 10Async thread do work!G100Async Thread start:CurrentThreadId is 9Async thread do work!Async CompletedCurrentThreadId is 10Person name is NicholasPerson age is 21Person ID is 000000000000000000Tick ID is G100G99Async Thread start:CurrentThreadId is 11Async thread do work!Async CompletedCurrentThreadId is 9Person name is NatePerson age is 38Person ID is 111111111111111111Tick ID is G99G98Async CompletedCurrentThreadId is 11Person name is NikiPerson age is 51Person ID is 333333333333333333Tick ID is G98Async Thread start:CurrentThreadId is 12Async thread do work!Main thread do work!G97Async Thread start:CurrentThreadId is 10Async thread do work!Async CompletedCurrentThreadId is 12Person name is VincentPerson age is 21Person ID is 222222222222222222Tick ID is G97G96Async Thread start:CurrentThreadId is 9Async thread do work!Async CompletedCurrentThreadId is 10Person name is GaryPerson age is 28Person ID is 444444444444444444Tick ID is G96G95Async Thread start:CurrentThreadId is 11Async thread do work!Async CompletedCurrentThreadId is 9Person name is CharlesPerson age is 49Person ID is 555555555555555555Tick ID is G95G94Async Thread start:CurrentThreadId is 12Async CompletedCurrentThreadId is 11Person name is KarlPerson age is 55Async thread do work!Person ID is 666666666666666666Tick ID is G94Main thread do work!G93Async Thread start:CurrentThreadId is 10Async thread do work!Async CompletedCurrentThreadId is 12Person name is KatharinePerson age is 19Person ID is 777777777777777777Tick ID is G93G92Async Thread start:CurrentThreadId is 9Async thread do work!Async CompletedCurrentThreadId is 10Person name is LeePerson age is 25Person ID is 888888888888888888Tick ID is G92G91Async CompletedCurrentThreadId is 9Person name is AnnPerson age is 34Person ID is 99999999999999999Tick ID is G91

③结论

一共创建了5个线程。

4. 通过TPL创建

从 .NET Framework 4 开始,TPL 是编写多线程代码和并行代码的首选方法。我们先看下MSDN的解释。

789.jpg

对于多线程,我们经常使用的是Thread。在我们了解Task之前,如果我们要使用多核的功能可能就会自己来开线程,然而这种线程模型在.net 4.0之后被一种称为基于“任务的编程模型”所冲击,因为task会比thread具有更小的性能开销,不过大家肯定会有疑惑,任务和线程到底有什么区别呢?

任务和线程的区别:

①任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行。

②任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。

4.1 通过Parallel类创建

①代码

static void Main(string[] args){    Ticket tic = new Ticket();    Person[] person = new Person[10]        {                    new Person("Nicholas", 21, "000000000000000000"),            new Person("Nate", 38, "111111111111111111"),            new Person("Vincent", 21, "222222222222222222"),            new Person("Niki", 51, "333333333333333333"),            new Person("Gary", 28, "444444444444444444"),            new Person("Charles", 49, "555555555555555555"),            new Person("Karl ", 55, "666666666666666666"),            new Person("Katharine", 19, "777777777777777777"),            new Person("Lee", 25, "888888888888888888"),            new Person("Ann", 34, "99999999999999999"),        };    Stopwatch watch = new Stopwatch();    ThreadMessage("MainThread start");    Console.WriteLine();    watch.Start();    Parallel.For(0, person.Length, item =>        {            BuyTicket(tic);        });    watch.Stop();    Console.WriteLine("Parallel running need " + watch.ElapsedMilliseconds + " ms.");     for (int i = 0; i < 3; i++)    {        Console.WriteLine("Main thread do work!");        Thread.Sleep(200);    }    Console.ReadKey();}

②运行结果

MainThread startCurrentThreadId is 9Async Thread start:CurrentThreadId is 9Async thread do work!G100Async Thread start:CurrentThreadId is 6Async thread do work!G99Async Thread start:CurrentThreadId is 10Async thread do work!G98Async Thread start:CurrentThreadId is 11Async thread do work!G97Async Thread start:CurrentThreadId is 12Async thread do work!G96Async Thread start:CurrentThreadId is 9Async thread do work!G95Async Thread start:CurrentThreadId is 6Async thread do work!G94Async Thread start:CurrentThreadId is 10Async thread do work!G93Async Thread start:CurrentThreadId is 11Async thread do work!G92Async Thread start:CurrentThreadId is 12Async thread do work!G91Parallel running need 534 ms.Main thread do work!Main thread do work!Main thread do work!

③结论

一共创建了5个线程。耗时534ms。

4.2 通过Task类创建

创建Task的方法有两种,一种是直接创建——new一个出来,一种是通过工厂创建。下面介绍工厂创建方法。

static void Main(string[] args){    Ticket tic = new Ticket();    Person[] person = new Person[10]        {            new Person("Nicholas", 21, "000000000000000000"),            new Person("Nate", 38, "111111111111111111"),            new Person("Vincent", 21, "222222222222222222"),            new Person("Niki", 51, "333333333333333333"),            new Person("Gary", 28, "444444444444444444"),            new Person("Charles", 49, "555555555555555555"),            new Person("Karl ", 55, "666666666666666666"),            new Person("Katharine", 19, "777777777777777777"),            new Person("Lee", 25, "888888888888888888"),            new Person("Ann", 34, "99999999999999999"),        };    Stopwatch watch = new Stopwatch();    ThreadPool.SetMaxThreads(1000, 1000);    ThreadPool.SetMinThreads(2, 2);    ThreadMessage("MainThread start");    Console.WriteLine();    Dictionary result = new Dictionary();    Task[] tasks = new Task[person.Length];    watch.Start();    for (int i = 0; i < person.Length; i++)    {        tasks[i] = Task.Factory.StartNew(() => (BuyTicket(tic)));                    }    Task.WaitAll(tasks, 5000);//设置超时5s    watch.Stop();    Console.WriteLine("Tasks running need " + watch.ElapsedMilliseconds + " ms." + "n");     for (int i = 0; i < tasks.Length; i++)    {        //超时处理        if (tasks[i].Status != TaskStatus.RanToCompletion)        {            Console.WriteLine("Task {0} Error!", i + 1);        }        else        {            //save result            result.Add(person[i], tasks[i].Result);            Console.WriteLine("Person name is " + person[i].Name);            Console.WriteLine("Person age is " + person[i].Age);            Console.WriteLine("Person ID is " + person[i].ID);            Console.WriteLine("Tick ID is " + tasks[i].Result);            Console.WriteLine();        }    }    for (int i = 0; i < 3; i++)    {        Console.WriteLine("Main thread do work!");        Thread.Sleep(200);    }    Console.ReadKey();}

②运行结果

MainThread startCurrentThreadId is 10Async Thread start:CurrentThreadId is 6Async thread do work!G100Async Thread start:CurrentThreadId is 11Async thread do work!G99Async Thread start:CurrentThreadId is 12Async thread do work!G98Async Thread start:CurrentThreadId is 13Async thread do work!G97Async Thread start:CurrentThreadId is 6Async thread do work!G96Async Thread start:CurrentThreadId is 11Async thread do work!G95Async Thread start:CurrentThreadId is 12Async thread do work!G94Async Thread start:CurrentThreadId is 13Async thread do work!G93Async Thread start:CurrentThreadId is 6Async thread do work!G92Async Thread start:CurrentThreadId is 11Async thread do work!G91Tasks running need 528 ms.Person name is NicholasPerson age is 21Person ID is 000000000000000000Tick ID is G100Person name is NatePerson age is 38Person ID is 111111111111111111Tick ID is G99Person name is VincentPerson age is 21Person ID is 222222222222222222Tick ID is G97Person name is NikiPerson age is 51Person ID is 333333333333333333Tick ID is G98Person name is GaryPerson age is 28Person ID is 444444444444444444Tick ID is G96Person name is CharlesPerson age is 49Person ID is 555555555555555555Tick ID is G95Person name is KarlPerson age is 55Person ID is 666666666666666666Tick ID is G94Person name is KatharinePerson age is 19Person ID is 777777777777777777Tick ID is G93Person name is LeePerson age is 25Person ID is 888888888888888888Tick ID is G92Person name is AnnPerson age is 34Person ID is 99999999999999999Tick ID is G91Main thread do work!Main thread do work!Main thread do work!

③结论

一共创建了5个线程。耗时528ms。

Task最吸引人的地方就是他的任务控制了,你可以很好的控制task的执行顺序,让多个task有序的工作。下面大概说一下:

Task.Wait:就是等待任务执行完成。

Task.WaitAll:就是等待所有的任务都执行完成。

Task.WaitAny:这个用法同Task.WaitAll,就是等待任何一个任务完成就继续向下执行。

Task.ContinueWith:就是在第一个Task完成后自动启动下一个Task,实现Task的延续。

Task的取消:通过cancellation的tokens来取消一个Task。

Task和线程池之间的抉择

1. 线程池

这里简要的分析下CLR线程池,其实线程池中有一个叫做“全局队列”的概念,每一次我们使用QueueUserWorkItem的使用都会产生一个“工作项”,然后“工作项”进入“全局队列”进行排队,最后线程池中的的工作线程以FIFO(First Input First Output)的形式取出,这里值得一提的是在.net 4.0之后“全局队列”采用了无锁算法,相比以前版本锁定“全局队列”带来的性能瓶颈有了很大的改观。

那么任务委托的线程池不光有“全局队列”,而且每一个工作线程都有”局部队列“。我们的第一反应肯定就是“局部队列“有什么好处呢?这里暂且不说,我们先来看一下线程池中的任务分配,如下图:

790.jpg

线程池的工作方式大致如下,线程池的最小线程数是6,线程1~3正在执行任务1~3,当有新的任务时,就会向线程池请求新的线程,线程池会将空闲线程分配出去,当线程不足时,线程池就会创建新的线程来执行任务,直到线程池达到最大线程数(线程池满)。总的来说,只有有任务就会分配一个线程去执行,当FIFO十分频繁时,会造成很大的线程管理开销。

2. Task

当我们new一个task的时候“工作项”就会进去”全局队列”,如果我们的task执行的非常快,那么“全局队列“就会FIFO的非常频繁,那么有什么办法缓解呢?

当我们的task在嵌套(见附录)的场景下,“局部队列”就要产生效果了,比如我们一个task里面有3个task,那么这3个task就会存在于“局部队列”中,如下图的任务一,里面有三个任务要执行,也就是产生了所谓的”局部队列”,当任务三的线程执行完成时,就会从任务一种的队列中以FIFO的形式”窃取”任务执行,从而减少了线程管理的开销。

这就相当于,有两个人,一个人干完了分配给自己的所有活,而另一个人却还有很多的活,闲的人应该接手点忙的人的活,一起快速完成。

791.jpg

从上面种种情况我们看到,这些分流和负载都是普通ThreadPool.QueueUserWorkItem所不能办到的,所以说在.net 4.0之后,我们尽可能的使用TPL,抛弃ThreadPool。

附录 Task的嵌套

1. 非关联嵌套

Task中的嵌套分为两种,关联嵌套和非关联嵌套,就是说内层的Task和外层的Task是否有联系,下面我们编写代码先来看一下非关联嵌套,及内层Task和外层Task没有任何关系,还是在控制台程序下面,代码如下:

static void Main(string[] args)      {         var pTask = Task.Factory.StartNew(() =>          {            var cTask = Task.Factory.StartNew(() =>            {               System.Threading.Thread.Sleep(2000);               Console.WriteLine("Childen task finished!");            });            Console.WriteLine("Parent task finished!");         });         pTask.Wait();         Console.WriteLine("Flag");         Console.Read();      }

运行后,输出以下信息:

792.jpg

从图中我们可以看到,外层的pTask运行完后,并不会等待内层的cTask,直接向下走先输出了Flag。这种嵌套有时候相当于我们创建两个Task,但是嵌套在一起的话,在Task比较多时会方便查找和管理,并且还可以在一个Task中途加入多个Task,让进度并行前进。

2. 关联嵌套

下面我们来看一下如何创建关联嵌套,就是创建有父子关系的Task,修改上面代码如下:

static void Main(string[] args)      {         var pTask = Task.Factory.StartNew(() =>          {            var cTask = Task.Factory.StartNew(() =>            {               System.Threading.Thread.Sleep(2000);               Console.WriteLine("Childen task finished!");            },TaskCreationOptions.AttachedToParent);            Console.WriteLine("Parent task finished!");         });         pTask.Wait();         Console.WriteLine("Flag");         Console.Read();      }

可以看到,我们在创建cTask时,加入了以参数,TaskCreationOptions.AttachedToParent,这个时候,cTask和pTask就会建立关联,cTask就会成为pTask的一部分,运行代码,看下结果:

793.jpg

可以看到,tTask会等待cTask执行完成。省得我们写Task.WaitAll了,外层的Task会自动等待所有的子Task完成才向下走。

3. 综合

下面我们来写一个Task综合使用的例子,来看一下多任务是如何协作的。假设有如下任务,如图:

794.jpg

任务2和任务3要等待任务1完成后,取得任务1的结果,然后开始执行。任务4要等待任务2完成,取得其结果才能执行,最终任务3和任务4都完成了,合并结果,任务完成。图中已经说的很明白了。下面来看一下代码:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace TaskDemo{    class Program    {        static void Main(string[] args)        {            Task.Factory.StartNew(() =>            {                var t1 = Task.Factory.StartNew(() =>                 {                    Console.WriteLine("Task 1 running...");                    return 1;                });                t1.Wait(); //等待任务一完成                var t3 = Task.Factory.StartNew(() =>                {                    Console.WriteLine("Task 3 running...");                    return t1.Result + 3;                });                var t4 = Task.Factory.StartNew(() =>                {                    Console.WriteLine("Task 2 running...");                    return t1.Result + 2;                }).ContinueWith(task =>                {                    Console.WriteLine("Task 4 running...");                    return task.Result + 4;                });                Task.WaitAll(t3, t4);  //等待任务三和任务四完成                var result = Task.Factory.StartNew(() =>                {                    Console.WriteLine("Task Finished! The result is {0}",t3.Result + t4.Result);                });            });            Console.Read();        }    }}

任务2和任务4可以用ContinueWith连接执行,最终运行结果如图:

795.jpg

可以看到所有的任务都执行了,我们也得到了正确的结果11.

以上就是从0自学C#11–多线程创建方法汇总以及优缺点的内容,更多相关内容请关注PHP中文网(www.php.cn)!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1432044.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月17日 06:09:42
从0自学C#10–在线绘制类图
下一篇 2025年12月17日 06:09:51

相关推荐

  • [openvino]windows上配置C++openvino后测试代码

    测试环境: vs2022 w_openvino_toolkit_windows_2024.3.0.16041.1e3b88e4e3f_x86_64.zip 代码: #include #include int main(int, char**) {// ——– 获取 OpenVINO 运行时…

    2026年8月29日
    100
  • 电脑显卡驱动冲突导致游戏崩溃故障排查及解决方案

    电脑显卡驱动冲突导致游戏崩溃故障排查及解决方案电脑显卡驱动冲突导致游戏崩溃故障排查及解决方案电脑显卡驱动冲突导致游戏崩溃故障排查及解决方案电脑显卡驱动冲突导致游戏崩溃故障排查及解决方案

    显卡驱动冲突导致游戏崩溃的解决方法包括使用ddu彻底卸载旧驱动、安装匹配的新驱动并进入安全模式操作。首先,下载ddu工具和官方稳定版显卡驱动,并断开网络连接;其次,进入安全模式运行ddu选择对应显卡品牌进行清理并重启;接着,不联网状态下安装新驱动选择“自定义”或“高级安装”并勾选“执行清洁安装”,优…

    2026年8月29日 用户投稿
    100
  • win11升级后软件不兼容怎么办_win11升级后软件兼容性处理方法

    首先使用兼容性疑难解答工具检测并修复问题,若无效则尝试手动设置兼容模式、以管理员身份运行、更新软件或启用.NET Framework等系统组件,最后可考虑虚拟机或替代软件方案。 如果您在将系统升级到 Windows 11 后发现某些软件无法正常运行,这通常是因为新系统的架构或安全策略与旧版软件存在冲…

    2026年8月28日
    100
  • kernel32.dll下载与安装指南 快速解决DLL缺失问题

    kernel32.dll下载与安装指南 快速解决DLL缺失问题kernel32.dll下载与安装指南 快速解决DLL缺失问题kernel32.dll下载与安装指南 快速解决DLL缺失问题kernel32.dll下载与安装指南 快速解决DLL缺失问题

    在使用windows操作系统时,有些用户可能会碰到程序启动时报错“无法找到kernel32.dll”或提示“kernel32.dll文件丢失”的问题。这类错误通常由系统文件损坏、病毒入侵或软件之间的冲突引起。下面为大家整理了几种有效的修复方法,一起来看看如何解决! 方法1:利用系统内置工具进行修复 …

    2026年8月28日 用户投稿
    100
  • C# 进程间通讯

    一、进程间通信的方式 1)共享内存 包括:内存映射文件、共享内存DLL、剪切板。 2)命名管道和匿名管道 3)消息通信 4)利用代理方法,例如SOCKET、配置文件、注册表方式等。 方法一:通信。 进程间通信的方式多种多样,常用的有共享内存(如内存映射文件、共享内存DLL、剪切板等)、命名管道和匿名…

    2026年8月28日
    200
  • 电脑提示缺少“api-ms-win-core-path-l1-1-0.dll”的4种解决方案

    电脑提示缺少“api-ms-win-core-path-l1-1-0.dll”的4种解决方案电脑提示缺少“api-ms-win-core-path-l1-1-0.dll”的4种解决方案电脑提示缺少“api-ms-win-core-path-l1-1-0.dll”的4种解决方案电脑提示缺少“api-ms-win-core-path-l1-1-0.dll”的4种解决方案

    当你在电脑上启动某些软件或游戏时,突然弹出提示:“缺少 api-ms-win-core-path-l1-1-0.dll 文件,程序无法启动”,这确实令人困扰。这通常是由于系统所需的运行库文件丢失或损坏所引起的。别担心,遇到此类问题无需慌张。本文将为你提供4种实用的解决方案,帮你快速修复缺失的 dll…

    2026年8月28日 用户投稿
    100
  • VSCode怎么看Memory_VSCode内存使用分析与性能检测教程

    首先通过任务管理器或VSCode内置进程资源管理器查看内存占用情况,再结合Chrome DevTools进行性能分析,重点关注CPU时间、内存分配、垃圾回收、渲染时间和长任务等指标,排查插件问题并优化设置以降低内存消耗。 VSCode的内存占用确实是个问题,尤其是在打开大型项目或者安装了大量插件之后…

    2026年8月28日
    200
  • 服务器资源(CPU/内存)的优化分配

    优化服务器上的cpu和内存资源可以通过以下步骤实现:1. cpu优化:通过任务调度和并行处理,如使用多线程编程提高cpu利用率。2. 内存优化:通过内存泄漏检测、选择合适的数据结构和缓存管理,如lru缓存来减少内存使用。持续监控和调整是关键,以提升系统性能和稳定性。 你想知道如何更好地优化服务器上的…

    2026年8月27日
    200
  • Web H5轻量级游戏开发,现在都用什么技术?

    Web H5轻量级游戏开发技术选型指南 Flash的退出给Web游戏开发带来了新的挑战,开发者们纷纷寻找替代方案来构建类似4399小游戏的轻量级H5游戏。尤其对于偏好JavaScript的前端开发者来说,选择合适的技术栈至关重要。本文针对Web H5轻量级游戏开发,探讨几种主流技术和框架。 目标是开…

    2026年8月27日
    200
  • 如何优雅地处理PHP异步操作?GuzzlePromises助你告别“回调地狱”!

    可以通过一下地址学习composer:学习地址 实际问题:php中的“阻塞”与“回调地狱” 想象一下这样的场景:你正在开发一个需要频繁与多个外部服务(比如第三方支付接口、短信通知服务、远程数据API)交互的PHP应用。传统的做法是,你发起一个请求,然后等待响应,再发起下一个请求,如此循环。这种同步(…

    用户投稿 2026年8月27日
    300
  • 协程调度(Scheduler)与上下文切换

    协程调度决定何时运行哪个协程,上下文切换则在调度过程中保存和恢复协程状态。1. 协程调度通过策略如优先级或轮转决定执行顺序,提高程序效率。2. 上下文切换通过关键字如yield或await实现,但频繁切换会增加性能开销。 协程调度与上下文切换是个既迷人又复杂的话题,让我们深入探讨一番。 在编程世界中…

    2026年8月27日
    100
  • 如何优雅地管理PHP异步操作:使用Composer引入GuzzleHttp/Promises

    Composer在线学习地址:学习地址 告别“回调地狱”:PHP异步操作的痛点 你是否曾遇到这样的场景:你的php应用需要从多个外部服务获取数据,或者执行一些耗时的后台任务。如果这些操作都是同步进行的,那么用户就得眼睁睁地看着页面转圈,直到所有操作完成。这不仅严重影响了用户体验,也浪费了服务器资源。…

    用户投稿 2026年8月27日
    100
  • 什么是java Java编程语言全面介绍

    java是一个强大的编程语言,适用于从小型应用到大型企业级系统的开发。其核心特点包括:一次编写,到处运行:通过jvm实现跨平台运行。面向对象编程:支持类、对象、继承和多态,增强代码组织和灵活性。集合框架:提供如arraylist等工具,简化数据处理。丰富的生态系统:包括异常处理、多线程、lambda…

    2026年8月26日
    000
  • java是c语言开发的吗 Java语言实现技术揭秘

    java 不是由 c++ 语言开发的,但受到了 c 和 c++ 的影响。java 的实现技术包括:1)虚拟机(jvm),将字节码转换为机器码,支持跨平台运行;2)标准库(java api),提供丰富功能和简洁语法;3)性能优化,如 jit 编译器和内存管理工具。java 是一个庞大而复杂的生态系统,…

    2026年8月26日
    000
  • RT-thread finsh移植到linux平台

    大家好,又见面了,我是你们的朋友全栈君。 目录 FinSH介绍 传统命令行模式 C 语言解释器模式 FinSH移植 移植要点 效果验证 代码下载 参考 在一次项目中, 需要进行嵌入式操作系统选型, 需求就是选择一款OS,既能满足当下项目的需要,又要考虑公司未来对物联网应用的扩展能力,对比了目前市面上…

    2026年8月26日
    100
  • Java中堆内存和栈内存的区别及内存管理机制

    Java中堆内存和栈内存的区别及内存管理机制Java中堆内存和栈内存的区别及内存管理机制Java中堆内存和栈内存的区别及内存管理机制Java中堆内存和栈内存的区别及内存管理机制

    堆内存用于存储对象实例,栈内存用于方法调用和局部变量。1. 堆内存由垃圾回收器管理,线程共享,生命周期长,适合存储动态分配的对象;2. 栈内存自动管理,线程私有,生命周期短,适合存储局部变量和方法调用帧;3. 区分两者是为了优化内存管理和性能;4. 堆溢出可通过分析内存泄漏、优化代码、增加堆内存等解…

    2026年8月26日 用户投稿
    200
  • 电脑找不到vcruntime140_1.dll无法继续执行代码的解决方法

    电脑找不到vcruntime140_1.dll无法继续执行代码的解决方法电脑找不到vcruntime140_1.dll无法继续执行代码的解决方法电脑找不到vcruntime140_1.dll无法继续执行代码的解决方法电脑找不到vcruntime140_1.dll无法继续执行代码的解决方法

    打开某个软件或游戏时,突然提示“找不到vcruntime140_1.dll,无法继续执行代码”?这类错误通常源于系统缺少必要的运行库支持。接下来,我们一起来看看几种实用的解决方案。 一、为什么会提示“vcruntime140_1.dll缺失”? vcruntime140_1.dll 是微软 Visu…

    2026年8月26日 用户投稿
    100
  • 安装错误0x80070002怎么解决 0x80070002错误代码解决

    安装错误0x80070002怎么解决 0x80070002错误代码解决安装错误0x80070002怎么解决 0x80070002错误代码解决安装错误0x80070002怎么解决 0x80070002错误代码解决安装错误0x80070002怎么解决 0x80070002错误代码解决

    在安装windows更新或部分软件时,不少用户会遭遇“错误代码:0x80070002”的提示。该问题在win7、win10乃至win11系统中均有发生,一旦出现,安装流程将被迫中断,导致更新或程序无法正常完成。为帮助大家高效应对这一故障,请跟随以下步骤逐一排查并解决。 一、问题成因梳理 错误代码 0…

    2026年8月25日 用户投稿
    200
  • 电脑提示DirectX错误导致玩不了游戏怎么办 4种实用方法

    电脑提示DirectX错误导致玩不了游戏怎么办 4种实用方法电脑提示DirectX错误导致玩不了游戏怎么办 4种实用方法电脑提示DirectX错误导致玩不了游戏怎么办 4种实用方法电脑提示DirectX错误导致玩不了游戏怎么办 4种实用方法

    directx是windows平台上运行游戏和图形应用的关键技术组件。当启动游戏时出现“directx错误”“缺少dx11/12”等提示,可能导致程序闪退、画面异常或无法正常运行。以下是几种有效的解决方式。 方法1:更新或修复DirectX组件 DirectX 12等新版组件通常随系统更新一并发布。…

    2026年8月25日 用户投稿
    100
  • php怎么获取行数_php获取文件行数的几种方法

    获取PHP文件行数的核心方法有四种:1. 使用file()函数将文件全部读入数组后统计元素个数,代码简洁但大文件易导致内存溢出;2. 用fgets()循环逐行读取并计数,内存占用低,适合大文件;3. 利用SplFileObject迭代器面向对象地逐行遍历,兼具可读性与效率;4. 在类Unix系统中调…

    2026年8月25日
    100

发表回复

登录后才能评论
关注微信