C#学习随手笔记之多线程编程

线程的概念:

线程(Thread)是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。(即线程是CPU调度的基本单位)

多线程的优缺点:

当前CPU运行速度太快,硬件处理速度跟不上,所以操作系统进行分时间片管理。这样,从宏观角度来说是多线程并发的,因为CPU速度太快,察觉不到,看起来是同一时刻执行了不同的操作。但是从微观角度来讲,同一时刻只能有一个线程在处理。(单核CPU情况),所以对于使用多线程来处理,能够是程序执行更加高效,并且能够实现并发量。但是在使用多线程时候,并不是越多越好,频繁的去创建多线程容易造成内存的损耗,当要并发的代码段十分简单,创建多个线程需要进行上下文切换等,并不会比单个线程块
所以说线程是一把双刃剑。如:
(1)线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多。

(2)多线程需要协调和管理,所以需要占用CPU时间以便跟踪线程。

(3)线程之间对共享资源的访问会相互影响,必须解决争用共享资源的问题。

(4)线程太多会导致控制太复杂,最终可能造成很多程序缺陷。

C#中的多线程编程

一、在默认的情况下,C#程序具有一个线程,此线程执行程序中以Main方法开始和结束的代码,Main()方法直接或间接执行的每一个命令都有默认线程(主线程)执行,当Main()方法返回时此线程也将终止。

二、在C#中,线程是使用Thread类处理的,该类在System.Threading命名空间中。使用Thread类创建线程时,只需要提供线程入口,线程入口告诉程序让这个线程做什么。通过实例化一个Thread类的对象就可以创建一个线程。创建新的Thread对象时,将创建新的托管线程。Thread类接收一个ThreadStart委托或ParameterizedThreadStart委托的构造函数。且有参的委托其参数类型必须为object类型

 //C#创建多线程,可以使用静态成员方法,也可以使用成员方法。
            MyThread mythread = new MyThread();
            Thread th1 = new Thread(new ThreadStart(mythread.TestThreadFunc2));
            Thread th2 = new Thread(new ThreadStart(MyThread.TestThreadFunc1));
            Thread th3 = new Thread(new ParameterizedThreadStart(mythread.TestThreadFunc3));

            th1.Start();
            th2.Start();
            th3.Start("parame");
            Console.ReadLine();
        }

        class MyThread
        {
            public static void TestThreadFunc1()
            {
                for(int i = 0; i < 5; i++)
                {
                    Console.WriteLine("I am a static func thread......");
                }
            }

            public void TestThreadFunc2()
            {
                for(int i = 0; i < 5; i++)
                {
                    Console.WriteLine("I am a sample func thread....");
                }
            }

            public void TestThreadFunc3(object name )
            {
                for(int i = 0; i < 5; i++)
                {
                    Console.WriteLine("I am a param func thread....{0}", name);
                }
            }
        }

线程中常用的属性

属性 说明
CurrentContext 获取线程正在其中执行的当前上下文。
CurrentThread 获取当前正在运行的线程。
ExecutionContext 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive 获取一个值,该值指示当前线程的执行状态。
IsBackground 获取或设置一个值,该值指示某个线程是否为后台线程。
IsThreadPoolThread 获取一个值,该值指示线程是否属于托管线程池。
ManagedThreadId 获取当前托管线程的唯一标识符。
Name 获取或设置线程的名称。
Priority 获取或设置一个值,该值指示线程的调度优先级。
ThreadState 获取一个值,该值包含当前线程的状态。
相关属性说明
  • ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。而Name是一个可变值,在默认时候,Name为一个空值 Null,开发人员可以通过程序设置线程的名称,但这只是一个辅助功能。
  • .NET为线程设置了Priority属性来定义线程执行的优先级别,里面包含5个选项,其中Normal是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别。分别为Lowest、BelowNormal、Normal、AboveNormal、Highest。
  • ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。一个应用程序域中可能包括多个上下文,而通过CurrentContext可以获取线程当前的上下文。
    当一个线程执函数执行完毕之后,其状态为Stopped,当其时间片未被分配为WaitSleepJoin状态。C#学习随手笔记之多线程编程_第1张图片
  • CurrentThread是最常用的一个属性,它是用于获取当前运行的线程的属性,包括线程ID,并且封装内存槽保存本地数据等。
  • IsBackground,设置前台后台线程。通过代码可以直观的看出。
mythread1.Count = 10;
            mythread2.Count = 20;
            Thread frontThread = new Thread(new ThreadStart(mythread1.RunLoop));
            frontThread.Name = "前台线程";
            Thread backThread = new Thread(new ThreadStart(mythread2.RunLoop));
            backThread.Name = "后台线程";
            backThread.IsBackground = true;

            backThread.Start();
            frontThread.Start();
        }

        class MyThread
        {
            public int Count { get; set; }
            public void RunLoop()
            {
                string threadName = Thread.CurrentThread.Name;
                for(int i = 0; i < Count; i++)
                {
                    Console.WriteLine("{0} Count : {1}", threadName, i);
                }
            }
        }

C#学习随手笔记之多线程编程_第2张图片
可以看到,后台程序没有执行完,程序就结束运行了。当我们把isBackground注释掉(默认的都为前台进程)。
C#学习随手笔记之多线程编程_第3张图片
程序需要等待所有前台线程执行完才结束。后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。

线程类的方法

方法名 功能
Abort() 终止本线程
GetDomain() 返回当前线程正在其中运行的当前域。
GetDomainId() 返回当前线程正在其中运行的当前域Id。
Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。
Join() 已重载。 阻塞调用线程,直到某个线程终止时为止。
Resume() 继续运行已挂起的线程。
Start() 执行本线程。
Suspend() 挂起当前线程,如果当前线程已属于挂起状态则此不起作用
Sleep() 把正在运行的线程挂起一段时间。

下面对Interrupt函数进行代码演示。


            sleeper = new Thread(new ThreadStart(SleepThread));
            interrupter = new Thread(new ThreadStart(InterruptThread));

            sleeper.Start();
            interrupter.Start();
            

        }
        public static Thread sleeper;
        public static Thread interrupter;
        static public void SleepThread()
        {
            for(int i = 0; i < 50; i++)
            {
                Console.WriteLine("SleepThread:{0}", i);
                if(i == 10 || i == 20 || i == 30)
                {
                    Console.WriteLine("在循环 {0} 处睡眠",i);

                    try
                    {
                        Thread.Sleep(1000);
                    }
                    catch
                    {

                    }

                }
            }
        }
        static public void InterruptThread()
        {
            while(true)
            {
                if(sleeper.ThreadState == ThreadState.WaitSleepJoin)
                {
                    Console.WriteLine("中断睡眠状态,继续工作");
                    sleeper.Interrupt();
                }
            }
        }

本来当i=10,20 30时,线程需要进入睡眠态,但是通过interrupt函数强制其不睡眠,继续进入Running态

线程同步

1.Lock(expression)
{
statement_block
}
expression代表你希望跟踪的对象:
如果你想保护一个类的实例,一般地,你可以使用this;
如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了
而statement_block就算互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

如下代码中运行就会出现资源的不同步现象

 static void Main(string[] args)
        {
            BookShop bookShop = new BookShop(1);
            Thread th1 = new Thread(bookShop.Sale);
            Thread th2 = new Thread(bookShop.Sale);
            Thread th3 = new Thread(bookShop.Sale);

            th1.Start();
            th2.Start();
            th3.Start();
            

        }
    }

    class BookShop
    {
        public int BookNumbers { get; set; }

        public BookShop(int count)
        {
            this.BookNumbers = count;
        }

        public void Sale()
        {
            int tmp = BookNumbers;
            if(tmp > 0)
            {
                Thread.Sleep(1000);
                BookNumbers -= 1;
                Console.WriteLine("售出一本书 还剩下{0}本书", BookNumbers);
            }
            else
            {
                Console.WriteLine("售空");
            }
        }
    }

运行结果如图
C#学习随手笔记之多线程编程_第4张图片
在多线程对BookNumbers访问时候,没有进行资源的同步互斥,导致结果出现异常,可以使用Lock进行处理,保证同一时间只能有一个线程对其进行访问

  lock(this)
            {
                int tmp = BookNumbers;
                if (tmp > 0)
                {
                    Thread.Sleep(1000);
                    BookNumbers -= 1;
                    Console.WriteLine("售出一本书 还剩下{0}本书", BookNumbers);
                }
                else
                {
                    Console.WriteLine("售空");
                }
            }

执行结果就正常了
C#学习随手笔记之多线程编程_第5张图片
lock的本质是将包围的语句块标记为临界区,这样一次只有一个线程进入临界区并执行代码。lock()语句中必须为引用类型在给lock传递参数时首先要避免使用public对象,因为有可能外部程序也在对这个对象加锁,这样就可能造成死锁

微软Lock注意的三种情况

lock(this)、lock(typeof(myType))、lock(“string”)。而上述三种情况微软都是不建议我们使用的。
在上诉代码中,我还是使用了lock(this),msdn的原话是lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁。
lock(this)就是锁定当前执行方法的实例对象,当内部进行加锁后,外部其他线程也对其进行加锁,就会造成死锁。

            MyLockDemo myLockDemo = new MyLockDemo();
            object ject = new object();
            Thread th = new Thread(new ParameterizedThreadStart(myLockDemo.LockFunc));
            th.Start(ject);

            lock(myLockDemo)
            {
                Console.WriteLine("主线程锁定myLockDemo,立马对ject锁定");
                lock(ject)
                {
                    Console.WriteLine("主线程对myLockDemo,ject都锁定");
                }
            }



        }

        class MyLockDemo
        {
            public void LockFunc(object obj)
            {
                lock(obj)
                {
                    Console.WriteLine("子线程对obj加锁,并且立马加锁MyLockDemo");
                    lock(this)
                    {
                        Console.WriteLine("完成对obj MyLockDemo都加锁");
                    }
                }
            }
        }

运行结果,发现出现了死锁,主线程一直在等待锁被释放执行C#学习随手笔记之多线程编程_第6张图片

WinForm中组件跨线程访问

C#学习随手笔记之多线程编程_第7张图片

private void button1_Click(object sender, EventArgs e)
        {
            Thread th = new Thread(new ThreadStart(Test));
            th.IsBackground = true;
            th.Start();
        }

        public void Test()
        {
            for(int i = 0; i < 10000; i++)
            {
                this.textBox1.Text = i.ToString();
            }
        }

点击测试,创建线程给Textbox赋值。
此时会报出异常,线程间操作无效,不是创建控件的textBox1访问它。
对此解决方案:
1.在窗体的加载事件中,将C#内置控件(Control)类的CheckForIllegalCrossThreadCalls属性设置为false,屏蔽掉C#编译器对跨线程调用的检查。

 Control.CheckForIllegalCrossThreadCalls = false;

使用上述的方法虽然可以保证程序正常运行并实现应用的功能,但是在实际的软件开发中,做如此设置是不安全的(不符合.NET的安全规范),在产品软件的开发中,此类情况是不允许的。如果要在遵守.NET安全标准的前提下,实现从一个线程成功地访问另一个线程创建的空间,要使用C#的方法回调机制。
2.使用回调函数

  //定义回调
        private delegate void setTextValueCallBack(int value);
         //声明回调
         private setTextValueCallBack setCallBack;
         private void Test()
         {
             for (int i = 0; i < 10000; i++)
             {               
                 //使用回调
                 textBox1.Invoke(setCallBack, i);
             }
         }
 
         /// 
         /// 定义回调使用的方法
         /// 
         /// 
         private void SetValue(int value)
         {
             this.textBox1.Text = value.ToString();
         }

异步多线程无序问题

 public SetBoxText setCallBack;
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            setCallBack = new SetBoxText(SetValue);
            Console.WriteLine("{0}Start", Thread.CurrentThread);

            Action action = this.Test;
            action.BeginInvoke(null, null);

            Console.WriteLine("Caculate over......");
        }

        public void Test()
        {
            for(int i = 0; i < 100; i++)
            {
                Console.WriteLine("is Caculator....");
                textBox1.Invoke(setCallBack, i);
             
            }
        }

        public void SetValue(int value)
        {
            this.textBox1.Text = value.ToString();
        }

在该代码中,使用BeginInvoke进行异步调用,发现程序执行结果并不是想象的,应该当计算结束后,才会提示运行结束,而运行结果如图。
C#学习随手笔记之多线程编程_第8张图片解决无序的问题,在C#中.NET框架中已经帮我们实现了回调在这里插入图片描述
BeginInvoke第二个参数就是回调。之后我们对代码进行改造,实现同步调用。

  Action action = this.Test;
            AsyncCallback callback = p =>
            {
                Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
            };
            action.BeginInvoke(callback, null);

运行结果
C#学习随手笔记之多线程编程_第9张图片

C#线程池

.NET Framework提供了包含ThreadPool类的System.Threading 空间,这是一个可直接访问的静态类,该类对线程池是必不可少的。它是公共“线程池”设计样式的实现。对于后台运行许多各不相同的任务是有用的。对于单个的后台线种而言有更好的选项。

线程的最大数量。这是完全无须知道的。在.NET中ThreadPool的所有要点是它自己在内部管理线程池中线程。多核机器将比以往的机器有更多的线程。微软如此陈述“线程池通常有一个线程的最大数量,如果所有的线程都忙,增加的任务被放置在队列中直到它们能被服务,才能作为可用的线程。

线程池所用的地方

线程池类型能被用于服务器和批处理应用程序中,线程池有更廉价的得到线程的内部逻辑,因为当需要时这些线程已被形成和刚好“连接”,所以线程池风格代码被用在服务器上。

MSDN表述:“线程池经常用在服务器应用程序中,每一个新进来的需求被分配给一个线程池中的线程,这样该需求能被异步的执行,没有阻碍主线程或推迟后继需求的处理。

适用线程池的地方:
  1. 不需要前台执行的线程
  2. 不需要在使用线程具有特定的优先级
  3. 线程的执行时间不易过长,否则会使线程阻塞。由于线程池具有最大线程数限制,因此大量阻塞的线程池线程可能会阻止任务启动。
  4. 不需要将线程放入单线程单元。所有 ThreadPool 线程均不处于多线程单元中
  5. 不需要具有与线程关联的稳定标识,或使某一线程专用于某一任务。

ThreadPool类

在多线程中,线程把大部分时间花费在等待状态,等待某个事件发生,然后才能给予相应,一般使用ThreadPool线程池来解决。另一种情况,线程都处于休眠状态,只是周期性的被唤醒,一般使用Timer定时器来解决。
ThreadPool类提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。该类提供一个由系统维护的线程池(可以看作一个线程的容器),该容器需要Windows 2000以上系统支持。

ThreadPool提供的方法
方法 描述
BindHandle 将操作系统句柄绑定到ThreadPool
GetAvailableThreads 检索由GetMaxThreads方法返回的最大线程池线程数和当前活动线程数之间的差值
GetMaxThreads 检索可以同时处于活动状态的线程池请求的数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用
GetMinThreads 检索线程池在新请求预测中维护的空闲线程数
QueueUserWorkItem 将方法排入队列以便执行。此方法在有线程池线程变得可用时执行
RegisterWaitForSingleObject 注册正在等待WaitHandle的委托
SetMaxThreads 设置可以同时处于活动状态的线程池的请求数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用
UnsafeQueueNativeOverlapped 将重叠的 I/O 操作排队以便执行
UnsafeQueueUserWorkItem 注册一个等待 WaitHandle 的委托
UnsafeRegisterWaitForSingleObject 将指定的委托排队到线程池
代码演示
 int workThreads;
            int workIo;

            ThreadPool.GetMaxThreads(out workThreads, out workIo);

            Console.WriteLine("设置前最大工作线程数:{0},最大IO数:{1}", workThreads, workIo);

            ThreadPool.SetMaxThreads(20, 10);


            ThreadPool.GetMaxThreads(out workThreads, out workIo);

            Console.WriteLine("设置后最大工作线程数:{0},最大IO数:{1}", workThreads, workIo);

            ThreadPool.GetMinThreads(out workThreads, out workIo);

            Console.WriteLine("设置前最小工作线程数:{0},最大IO数:{1}", workThreads, workIo);

            if(ThreadPool.SetMinThreads(10, 30))
            {
                //设置成功
                ThreadPool.GetMinThreads(out workThreads, out workIo);

                Console.WriteLine("设置后最小工作线程数:{0},最大IO数:{1}", workThreads, workIo);
            }

即使是在所有线程都处于空闲状态时,线程池也会维持最小的可用线程数,以便队列任务可以立即启动。将终止超过此最小数目的空闲线程,以节省系统资源。
通过上述方法,来设置最大最小辅助线程数目和异步Io数,
C#学习随手笔记之多线程编程_第10张图片
设置最小线程数时候,函数设置不成功返回false。

加入队列

    ThreadPoolFunc poolFunc = new ThreadPoolFunc();

            ThreadPool.QueueUserWorkItem(new WaitCallback(poolFunc.Func1), 3);
            ThreadPool.QueueUserWorkItem(new WaitCallback(poolFunc.Func2), 2);

            Console.ReadLine();

        }
        class ThreadPoolFunc
        {
            public void Func1(object o)
            {
                while(true)
                {
                    Console.WriteLine("I am Func1.....");
                }
                
            }

            public void Func2(object o)
            {
                while(true)
                {
                    Console.WriteLine("I am Func2:{0}", (int)o);
                }
            }

ThreadPool的线程都是后台线程,当主线程结束了之后,会自动结束线程。

C# Task

thread和threadpool,这都是异步操作,threadpool其实就是thread的集合,具有很多优势,不过在任务多的时候全局队列会存在竞争而消耗资源。thread默认为前台线程,主程序必须等线程跑完才会关闭,而threadpool相反。

总结:threadpool确实比thread性能优,但是两者都没有很好的api区控制,如果线程执行无响应就只能等待结束,从而诞生了task任务。
Task

Task简单的看就是任务,其背后实现与使用了线程池,但它的性能优于ThradPool,因为它使用的队列不是全局队列,而使用的是本地队列。使得资源的竞争变少。同时Task类还提供了丰富的API来控制管理线程, 在多核CPU情况下Task的性能优于Thread与ThreadPool,而在单核情况下三者性能没有太大区别。

Task的创建
  1. new 创建(不会直接运行,需要使用Start才会开始运行)
 var task = new Task(() =>
            {
                Console.WriteLine("I am new Task...");
            });

            task.Start();
  1. factory创建,会直接运行。
var facTask = Task.Factory.StartNew(() =>
           {
               Console.WriteLine("I am fac task.");
           });

Task的生命周期

var testTask = new Task(() =>
            {
                Console.WriteLine("task start");
                System.Threading.Thread.Sleep(2000);
            });
            Console.WriteLine(testTask.Status);
            testTask.Start();
            Console.WriteLine(testTask.Status);
            Console.WriteLine(testTask.Status);
            testTask.Wait();
            Console.WriteLine(testTask.Status);
            Console.WriteLine(testTask.Status);

C#学习随手笔记之多线程编程_第11张图片

Task的“Wait”方法
  • Wait()方法
 var testTask = new Task(() =>
            {
                while(true)
                {
                    Console.WriteLine("task start");
                    System.Threading.Thread.Sleep(1000);
                }

            });

            testTask.Start();
            testTask.Wait();

            while(true)
            {
                Thread.Sleep(1000);
                Console.WriteLine("Main Thread...");
            }

Wait方法,对单个Task等待,等待任务执行完。

  • WaitAll()方法
    对多个Task进行等待
  • WaitAny()执行任意一个Task就往下执行。
var testTask = new Task(() =>
            {
                Console.WriteLine("task start");
                System.Threading.Thread.Sleep(2000);
            });
            testTask.Start();
            var factoryTeak = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("factory task start");
            });
            Task.WaitAny(testTask);
            Console.WriteLine("end");
Task的回调执行

使用ContinueWith(Func continuationFunction);方法创建一个完成Task的并返回一个值的异步延时操作

var testTask = new Task(() =>
            {
                Console.WriteLine("task start");
                System.Threading.Thread.Sleep(2000);
            });
            testTask.Start();
            var resultTest = testTask.ContinueWith<string>((Task) => {
                Console.WriteLine("testTask end");
                return "end";
            });
            Console.WriteLine(resultTest.Result);

async/await

async/await特性是与Task紧密相关的。它们也是用于异步操作,若声明一个async的函数没有使用await,则编译器也会报出警告,会将其当做同步方法。

async/await使用场景

在C# GUI编程时,如有些比较耗时的操作时,通过新开一个线程去处理,而不是在主线程中去处理,以免造成UI刷新阻塞线程。

本处,最近我在编写C#文件管理器时,根据Windows API获取历史文件访问时,这个操作比较费时,若使用同步方法,则在获取过程中会造成UI界面卡死,等待时间过长,出现任务无响应。

使用async/await方式,能够避免使用传统方法Thread类造成的回调地狱。所以C#更偏向使用async/await 方式来进行异步操作。

async/await调用过程
            GetValueAsync();

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

        }

        static async Task GetValueAsync()
        {
            await Task.Run(() =>
            {
                Thread.Sleep(1000);
                for(int i = 0; i < 5; i++)
                {
                    Console.WriteLine("Format task:{0}",i);
                }

            });

            Console.WriteLine("Task END.");
        }

C#学习随手笔记之多线程编程_第12张图片

  1. 调用GetValueAsync(),此时为同步调用。
  2. 执行Task.Run() 生成一个新的线程执行,返回Task对象。
  3. 由于调用Task.Run时,是以Wait修饰的,所以为异步调用,上下文环境中保存上一步的Task对象。在此处发生调用流阻塞,而当前的调用语句便是调用流阻塞点,于是发生调用流阻塞返回,执行流回到AysncCall()的GetValueAsync()处,并执行下一步
  4. 由于新建了一个线程来执行,并且在线程中使用了Sleep阻塞,计算机运行速度够快会先执行Main End。到ReadKey()函数
  5. 执行流(强制被)跳转到调用流阻塞点,即从调用流阻塞点恢复执行流,发生了调用流阻塞异步完成跳转,于是打印Task End。
缺少await关键。

若在方法中缺少await关键字,在编译器中会报出警告,此时执行流不会遇到流阻塞点,会继续执行下去。
C#学习随手笔记之多线程编程_第13张图片
执行流不会在Task.Run()这里停下返回,而是直接向下执行。但是新的线程还是会被创建出来。但是这种情况,程序就不会去等待Task.Run()完成了。

最后一说

C#中规定Main()函数是不能够为异步方法,这说明了任何异步调用都是同步调用的子调用。
async/await是不会主动创建线程(Task)的,创建线程的工作还是交给程序员来完成;async/await说白了就只是用来提供阻塞调用点的关键字而已。

Task取消

使用CancellationTokenSource来取消Task。

 var tokenSource = new CancellationTokenSource();

            var task = new Task(() =>
            {
                for(int i = 0; i < 6; i++)
                {
                    Console.WriteLine("Thread Run....");
                    Thread.Sleep(1000);
                }
            }, tokenSource.Token);
            //task.Start();
            Console.WriteLine(task.Status);

            tokenSource.Cancel();

            Console.WriteLine(task.Status);

            Console.Read();
Task嵌套
  1. 普通关联,父Task对子Task没有影响。
var parentTask = new Task(() => {
                var childTask = new Task(() => {
                    System.Threading.Thread.Sleep(2000);
                    Console.WriteLine("childTask to start");
                });
                childTask.Start();
                Console.WriteLine("parentTask to start");
            });
            parentTask.Start();
            parentTask.Wait();
            Console.WriteLine("end");
            Console.ReadLine();
  1. 父task子task关联,wait会一直等待父子执行完。
            var parentTask = new Task(() => {
                var childTask = new Task(() => {
                    System.Threading.Thread.Sleep(2000);
                    Console.WriteLine("childTask to start");
                }, TaskCreationOptions.AttachedToParent);
                childTask.Start();
                Console.WriteLine("parentTask to start");
            });
            parentTask.Start();
            parentTask.Wait();
            Console.WriteLine("end");

C#学习随手笔记之多线程编程_第14张图片

ManualResetEvent

表示线程同步事件,收到信号时,必须手动重置该事件。 无法继承此类。

其构造函数为

public ManualResetEvent (bool initialState);

initialState 表示是否初始化,如果为 true,则将初始状态设置为终止(不阻塞);如果为 false,则将初始状态设置为非终止(阻塞)。

有两个方法

//将事件状态设置为终止状态,从而允许继续执行一个或多个等待线程。
public bool Set ();

//将事件状态设置为非终止,从而导致线程受阻。
public bool Reset ();

C#学习随手笔记之多线程编程_第15张图片

你可能感兴趣的:(C#)