【C#】线程操作总结

1. 线程基础

.1. 生命周期

在线程的生命周期中,它要经过新建(New)就绪(Runnable)、行(Running)阻塞(Blocked)死亡(Dead)五种状态。当线程start后,它不能一直"独占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换.

  • 新建状态(new):指新建了一个线程对象。Thread t1 =new Thread();这里就新建了一个Thread类的线程对象。
  • 就绪状态(Runnable):当线程对象创建后,该线程对象自身或者其他对象调用了该对象的start()方法。该线程就位于了可运行池中,变的可运行,等待获取cpu的使用权。因为在同一时间里cpu只能执行某一个线程。
  • 运行状态(Running): 当就绪状态的线程获取了cpu的时间片或者说获取了cpu的执行时间,这时就会调用该线程对象的run()方法,然后就从就绪状态就入了运行状态。
  • 阻塞状态(Blocked):**阻塞状态就是线程因为某种原因暂时放弃了对cpu的使用权,暂时停止运行。**直到线程再次进入就绪状态,才有机会转到运行状态。阻塞状态分为三种情况:
    • 等待阻塞:运行状态的线程调用了wait()方法后,该线程会释放它所持有的锁,然后被jvm放入到等待池中,只有等其他线程调用Object类的notify()方法或者norifyAll()方法时,才能进入重新进入到就绪状态
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,JVM就会把该线程设置为阻塞状态,一直到线程获取到同步锁,才能转入就绪状态。
    • 其它阻塞:运行的线程在执行sleep()或者join()方法时,或者发出了I/O请求,JVM就会把该线程设置为阻塞状态,当sleep()状态超时、join()等待等待线程终止或者超时、或者I/O处理完毕时,线程重进转入到就绪状态。在这需要注意的是sleep()方法和wait()不同,sleep不会释放自身所持有的锁。
  • **死亡状态(Dead):**当线程执行完了或者因异常退出了run()的执行,该线程的生命周期就结束了。
.2. 线程优先级
  • 记住当线程的优先级没有指定时,所有线程都携带普通优先级
  • java优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
  • 记住优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态
  • 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
  • 由调度程序决定哪一个线程被执行。
  • t.setPriority()用来设定线程的优先级。
  • 记住在线程开始方法被调用之前,线程的优先级应该被设定
  • 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级。
.1. c# demo
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Current thread priority: {0}", Thread.CurrentThread.Priority);
        Console.WriteLine("Running on all cores available");
        RunThreads();
        Thread.Sleep(TimeSpan.FromSeconds(2));
        Console.WriteLine("Running on a single core");
        Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
        RunThreads();
    }

    static void RunThreads()
    {
        var sample = new ThreadSample();

        var threadOne = new Thread(sample.CountNumbers);
        threadOne.Name = "ThreadOne";
        var threadTwo = new Thread(sample.CountNumbers);
        threadTwo.Name = "ThreadTwo";

        threadOne.Priority = ThreadPriority.Highest;
        threadTwo.Priority = ThreadPriority.Lowest;
        threadOne.Start();
        threadTwo.Start();

        Thread.Sleep(TimeSpan.FromSeconds(2));
        sample.Stop();

        Console.ReadKey();
    }

    class ThreadSample
    {
        private bool _isStopped = false;

        public void Stop()
        {
            _isStopped = true;
        }

        public void CountNumbers()
        {
            long counter = 0;

            while (!_isStopped)
            {
                counter++;
            }

            Console.WriteLine("{0} with {1,11} priority " +
                        "has a count = {2,13}", Thread.CurrentThread.Name,
                        Thread.CurrentThread.Priority,
                        counter.ToString("N0"));
        }
    }
}
.2. java demo
//或取优先级
public static void main(String[] args) {
    System.out.println(Thread.currentThread().getPriority());
}

//设置优先级
public static void main(String[] args) {
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
    System.out.println(Thread.currentThread().getPriority());
    Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
    System.out.println(Thread.currentThread().getPriority());
    Thread.currentThread().setPriority(8);
    System.out.println(Thread.currentThread().getPriority());
}

//不同优先级设置
class CustomThread extends Thread {
    public CustomThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}

public static void main(String[] args) {
    Thread t1 = new CustomThread("A");
    Thread t2 = new CustomThread("B");
    t1.setPriority(Thread.MIN_PRIORITY);
    t2.setPriority(Thread.MAX_PRIORITY);
    t1.start();
    t2.start();
}
.3. 前台线程&后台线程

应用程序必须运行完所有的前台线程才会完全退出若前台线程未执行完成,关闭应用程序后,应用程序并没有完全退出,在任务管理器中还存在此进程;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束

  • 后台线程:后台线程是可以随时被CLR关闭而不引发异常的,也就是说当后台线程被关闭时,资源的回收是立即的,不等待的,也不考虑后台线程是否执行完成,就算是正在执行中也立即被终止。【后台,存在于黑暗之中默默无闻,它的消亡和存在,别人也感受不到】
  • 前台线程:前台线程是不会被立即关闭的,它的关闭只会发生在自己执行完成时,不受外在因素的影响。假如应用程序退出,造成它的前台线程终止,此时CLR仍然保持活动并运行,使应用程序能继续运行,当它的的前台线程都终止后,整个进程才会被销毁。
  • 应用程序的主线程以及使用Thread构造的线程都默认为前台线程, 线程池线程也就是使用 ThreadPool.QueueUserWorkItem()Task工厂创建的线程都默认为后台线程
  • 可以在任何时候将前台线程修改为后台线程,方式是设置Thread.IsBackground 属性。
  • 不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止
  • UI 一般使用前台进程处理计算繁琐的一般放在后台进程,否则会导致UI卡住。
.1. c# demo
class Program
{
    static void Main(string[] args)
    {
        var sampleForeground = new ThreadSample(10);
        var sampleBackground = new ThreadSample(20);

        var threadOne = new Thread(sampleForeground.CountNumbers);
        threadOne.Name = "ForegroundThread";
        var threadTwo = new Thread(sampleBackground.CountNumbers);
        threadTwo.Name = "BackgroundThread";
        threadTwo.IsBackground = true;

        threadOne.Start();
        threadTwo.Start();

        Console.ReadKey();
    }

    class ThreadSample
    {
        private readonly int _iterations;

        public ThreadSample(int iterations)
        {
            _iterations = iterations;
        }
        public void CountNumbers()
        {
            for (int i = 0; i < _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);
            }
        }
    }
}

.4. lock/Monitor

在多线程代码中,多个线程可能会访问一些公共的资源(变量、方法逻辑等等),这些公共资源称为临界区(共享区);临界区的资源是不安全,所以需要通过线程同步对多个访问临界区的线程进行控制.

lock关键字可以锁住任何object类型及其派生类,但是尽量不要用public 类型的,否则实例将超出代码的控制范围。根据MSDN,常见的结构 lock (this)、lock (typeof (MyType)) 和 lock (“myLock”) 违反此准则:

  • 如果实例可以被公共访问,将出现 lock (this) 问题
  • 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题
  • 由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。
  • 最好定义 private 对象 或 private static 对象进行上锁,从而保护所有实例所共有的数据。
.1. lock C# demo
namespace LockTest
{
    class PrintNum
    {
        private object lockObj = new object();

        public void PrintOddNum()
        {
            lock (lockObj)
            {
                Console.WriteLine("Print Odd numbers:");
                for (int i = 0; i < 10; i++)
                {
                    if(i%2 != 0)
                        Console.Write(i);
                    Thread.Sleep(100);
                }
                Console.WriteLine();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            PrintNum printNum = new PrintNum();
            for (int i = 0; i < 3; i++)
            {
                Thread temp = new Thread(new ThreadStart(printNum.PrintOddNum));
                temp.Start();
            }

            Console.Read();
        }
    }
}
.2. lock c# 错误代码
#因为Main函数和PrintNum类型中都对printNum对象进行了加锁,所以当主线程获得了互斥锁之后,其他子线程都被block住了,没有办法执行PrintOddNum方法了。
namespace LockTest
{
    class PrintNum
    {
        private object lockObj = new object();

        public void PrintOddNum()
        {
            lock (this)
            {
                Console.WriteLine("Print Odd numbers:");
                for (int i = 0; i < 10; i++)
                {
                    if (i % 2 != 0)
                        Console.Write(i);
                    Thread.Sleep(100);
                }
                Console.WriteLine();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            PrintNum printNum = new PrintNum();
            for (int i = 0; i < 3; i++)
            {
                Thread temp = new Thread(new ThreadStart(printNum.PrintOddNum));
                temp.Start();
            }

            lock (printNum)
            {
                Thread.Sleep(5000);
                Console.WriteLine("Main thread will delay 5 seconds");
            }

            Console.Read();
        }
    }
}

.3. Monitor

lock使用的就是Monitor类型的Enter和Exit方法。很多情况下lock就可以满足需求了,但是当我们需要更进一步的线程同步时,就需要使用Monitor类型了。

  • public static void Enter(object obj);
    • 在指定对象上获取互斥锁
  • public static void Exit(object obj);
    • 释放指定对象上的互斥锁
  • public static void Pulse(object obj);
    • 通知等待队列中的线程锁定对象状态的更改
  • public static bool TryEnter(object obj);
    • 试图获取指定对象的互斥锁,如果获得了互斥锁就返回true;否则返回false
    • TryEnter(Object, Int32)形式,表示在指定的毫秒数内尝试获取指定对象上的互斥锁
  • public static bool Wait(object obj);
    • 释放对象上的锁并阻止当前线程,直到它重新获取该锁
  • Wait:当线程调用 Wait 时,它释放对象的锁并进入等待队列。对象的就绪队列中的下一个线程(如果有)获取锁并拥有对对象的独占使用。所有调用 Wait 的线程都将留在等待队列中,直到它们接收到由锁的所有者发送的 Pulse 或 PulseAll 的信号为止。
  • Pulse:只有锁的当前所有者可以使用 Pulse 向等待对象发出信号。如果发送了 Pulse,则只影响位于等待队列最前面的线程。如果发送了 PulseAll,则将影响正等待该对象的所有线程。接收到信号后,一个或多个线程将离开等待队列而进入就绪队列。 在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不一定是接收到脉冲的线程)将获得该锁

使用注意事项:

  • 在使用Enter和Exit方法的时候,建议像lock的IL代码一样,使用try/finally语句块对Enter和Exit进行包装
  • Pulse 、PulseAll 和 Wait 方法必须从同步的代码块内调用。
  • 在使用Pulse/Wait进行线程同步的时候,一定要牢记,Monitor 类不对指示 Pulse 方法已被调用的状态进行维护。 因此,如果在没有等待线程时调用 Pulse,则下一个调用 Wait 的线程将阻止,似乎 Pulse 从未被调用过。 如果两个线程正在使用 Pulse 和 Wait 交互,则可能导致死锁
namespace MointorTest
{
    class Program
    {
        private static object monitorObj = new object();

        static void Main(string[] args)
        {
            Thread firstThread = new Thread(new ThreadStart(TryEnterTest));
            firstThread.Name = "firstThread";
            Thread secondThread = new Thread(new ThreadStart(TryEnterTest));
            secondThread.Name = "secondThread";
            firstThread.Start();
            secondThread.Start();
            Console.Read();

        }
        public static void TryEnterTest()
        {
            if (!Monitor.TryEnter(monitorObj, 5000))
            {
                Console.WriteLine("Thread {0} wait 5 seconds, didn't get the lock", Thread.CurrentThread.Name);
                Console.WriteLine("Thread {0} completed!", Thread.CurrentThread.Name);
                return;
            }
            try
            {
           Monitor.Enter(monitorObj);
                Console.WriteLine("Thread {0} get the lock and will run 10 seconds", Thread.CurrentThread.Name);
                Thread.Sleep(10000);
                Console.WriteLine("Thread {0} completed!", Thread.CurrentThread.Name);
            }
            finally
            {
                Monitor.Exit(monitorObj);
            }
        }
    }
}
.4. 互斥队列
namespace BlockingQueue
{
    class BlockingQueue<T>
    {
        private object lockObj = new object();
        public int QueueSize { get; set; }
        private Queue<T> queue;
        
        public BlockingQueue()
        {
            this.queue = new Queue<T>(this.QueueSize);
        }

        public bool EnQueue(T item)
        {
            lock (lockObj)
            {
                while (this.queue.Count() >= this.QueueSize)
                {
                    Monitor.Wait(lockObj);
                }
                this.queue.Enqueue(item);
                Console.WriteLine("---> 0000" + item.ToString());
                Monitor.PulseAll(lockObj);
            }
            return true;

        }

        public bool DeQueue(out T item)
        {
            lock (lockObj)
            {
                while (this.queue.Count() == 0)
                {
                    if (!Monitor.Wait(lockObj, 3000))
                    {
                        item = default(T);
                        return false;
                    };
                }
                item = this.queue.Dequeue();
                Console.WriteLine("     0000" + item + " <---");
                Monitor.PulseAll(lockObj);
            }
            return true;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            BlockingQueue<string> bQueue = new BlockingQueue<string>();
            bQueue.QueueSize = 3;

            Random ran = new Random();

            //producer
            new Thread(
            () => {
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(ran.Next(1000));
                    bQueue.EnQueue(i.ToString());
                    
                }
                Console.WriteLine("producer quit!");
            }).Start();

            //producer
            new Thread(
            () =>
            {
                for (int i = 5; i < 10; i++)
                {
                    Thread.Sleep(ran.Next(1000));
                    bQueue.EnQueue(i.ToString());

                }
                Console.WriteLine("producer quit!");
            }).Start();

            //consumer
            new Thread(
            () =>
            {
                while (true)
                {
                    Thread.Sleep(ran.Next(1000));
                    string item = string.Empty;
                    if (!bQueue.DeQueue(out item))
                    {
                        break;
                    };
                }
                Console.WriteLine("consumer quit!");
            }).Start();

            Console.Read();
        }
    }
}

2. 线程同步

.1. 原子操作&上下文切换

即使是单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。

  • 用户级上下文: 正文、数据、用户堆栈以及共享存储区;
  • 寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
  • 系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。

原子操作是指不会被 线程调度 机制打断的操作; 这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。

.2. 内核模式&用户模式&混合模式

内核空间和用户空间是现代操作系统的两种工作模式,内核模块运行在内核空间,而用户态应用程序运行在用户空间。它们代表不同的级别,而对系统资源具有不同的访问权限。内核模块运行在最高级别(内核态),这个级下所有的操作都受系统信任,而应用程序运行在较低级别(用户态)。在这个级别,处理器控制着对硬件的直接访问以及对内存的非授权访问。内核态和用户态有自己的内存映射,即自己的地址空间。

  • 内核态,运行于进程上下文,内核代表进程运行于内核空间;
  • 内核态,运行于中断上下文,内核代表硬件运行于内核空间;
  • 用户态,运行于用户空间。
.3. 同步信号量

信号量(semaphore)是一种用于提供不同进程之间或者一个给定的不同线程间同步手段的原语。信号量多用于进程间的同步与互斥,简单的说一下同步和互斥的意思:

  • 同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的个先后执行顺序。
  • 互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)。
  • 竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程。

共享资源通常分为两类:一类是互斥共享资源,即任一时刻只允许一个进程访问该资源;另一类是同步共享资源,即同一时刻允许多个进程访问该资源;信号量是解决互斥共享资源的同步问题而引入的机制。

3. 线程池

.1. APM

Add方法中模拟耗时操作(2s)和Main方法中模拟耗时操作(3s)是串行执行的,那么我们有没有一种方法使这两种操作并行执行了?(3s中之内搞定这两个耗时操作)。

.2. 线程取消

如果一个任务能够在正常完成之前被外部代码影响进入终结状态,那么这个任务就是可取消的。直觉上看,每个任务都应该能够被外界的命令所取消。

  1. 用户请求取消。用户通过交互界面显式地发起取消请求,例如取消正在下载的任务。
  2. 有时间限制的操作。特别是在大数据的背景下,某些关键路径的操作不可超过一定时延。超时的情况下宁可返回错误提示用户重试,也不可以继续等待导致吞吐下降甚至级联故障。
  3. 应用程序事件。例如,通过多线程执行 anyOf 逻辑时,有一个子任务取得结果,则可以乃至应该取消其他子任务。
.3. 等待&延时

Sleep 的过程中,进程是不能响应外部操作的,变成了假死状态

.1. sleep
using System.Threading;  //导入命名空间,类Thread就在此空间中Thread.Sleep(2000); //延时2s
.2. DispatcherTimer
tbkLabel.Text = "two seconds delay";var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };timer.Start();timer.Tick += (sender, args) =>    {        timer.Stop();        var page = new Page2();        page.Show();    };
.3. Task.Delay
tbkLabel.Text = "two seconds delay";Task.Delay(2000).ContinueWith(_ =>    {      var page = new Page2();     page.Show();   });
.4. 异步/等待
// we need to add the async keyword to the method signaturepublic async void TheEnclosingMethod(){    tbkLabel.Text = "two seconds delay";    await Task.Delay(2000);    var page = new Page2();    page.Show();}
.4. BackgroundWorker

BackgroundWorker是.net里用来执行多线程任务的控件,它允许编程者在一个单独的线程上执行一些操作。耗时的操作(如下载和数据库事务)在长时间运行时可能会导致用户界面 (UI) 始终处于停止响应状态。如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用BackgroundWorker类方便地解决问题。

该控件有三个事件:DoWorkProgressChangedRunWorkerCompleted在程序中调用RunWorkerAsync方法则会启动DoWork事件的事件处理,当在事件处理过程中,调用 ReportProgress方法则会启动ProgressChanged事件的事件处理,而当DoWork事件处理完成时,则会触发RunWorkerCompleted事件

  • 确保在 DoWork 事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChanged和 RunWorkerCompleted 事件与用户界面进行通信
namespace BwTester{public partial class Form1 : Form{    public Form1()    {        InitializeComponent();    }        BackgroundWorker backgroundWorker;        private void button1_Click(object sender, EventArgs e)    {        backgroundWorker = new BackgroundWorker();        backgroundWorker.WorkerReportsProgress = true;        backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);        backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);        backgroundWorker.RunWorkerAsync();    }    void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)    {        progressBar1.Value = e.ProgressPercentage;    }    void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)    {        for (int i = 0; i < 500; i++)        {            backgroundWorker.ReportProgress(i);            Thread.Sleep(100);        }    }}

4. 并行任务库

.1. 任务创建及组合

线程是创建并发的底层工具,因此具有一定的局限性。

  • 没有简单的方法可以从联合(Join)线程得到“返回值”。因此必须创建一些共享域。当抛出一个异常时,捕捉和处理异常也是麻烦的。
  • 线程完成之后,无法再次启动该线程。相反,只能联合(Join)它(在进程阻塞当前线程)。

Task 类的表示单个操作不返回一个值,通常以异步方式执行。Task 对象是一个的中心思想 基于任务的异步模式 首次引入.NET Framework 4 中。 因为由执行工作 Task 对象通常以异步方式执行在线程池线程上而不是以同步方式在主应用程序线程,您可以使用 Status 属性,以及 IsCanceled, ,IsCompleted, ,和 IsFaulted 属性,以确定任务的状态。 大多数情况下,lambda 表达式用于指定的任务是执行的工作。

  • task.Resut获取结果时会阻塞线程,即如果task没有执行完成,会等待task执行完成获取到Result,然后再执行后边的代码,
.1. Task 创建
static void Main(string[] args){    //1.new方式实例化一个Task,需要通过Start方法启动    Task task = new Task(() =>                         {                             Thread.Sleep(100);                             Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");                         });    task.Start();    //2.Task.Factory.StartNew(Action action)创建和启动一个Task    Task task2 = Task.Factory.StartNew(() =>                                       {                                           Thread.Sleep(100);                                           Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");                                       });    //3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task, 后台线程    Task task3 = Task.Run(() =>                          {                              Thread.Sleep(100);                              Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");                          });    Console.WriteLine("执行主线程!");    Console.ReadKey();}//先打印"执行主线程",然后再打印各个任务,说明了Task不会阻塞主线程。上边的栗子Task都没有返回值,我们也可以创建有返回值的~task,用法和没有返回值的基本一致,
.2. Task 同步
 static void Main(string[] args) {     Task task = new Task(() =>                          {                              Thread.Sleep(100);                              Console.WriteLine("执行Task结束!");                          });     //同步执行,task会阻塞主线程     task.RunSynchronously();     Console.WriteLine("执行主线程结束!");     Console.ReadKey(); }
.3. Task 阻塞
  • Wait/WaitAny/WaitAll
  static void Main(string[] args)  {      Task task1 = new Task(() => {          Thread.Sleep(500);          Console.WriteLine("线程1执行完毕!");      });      task1.Start();      Task task2 = new Task(() => {          Thread.Sleep(1000);          Console.WriteLine("线程2执行完毕!");      });      task2.Start();      //阻塞主线程。task1,task2都执行完毕再执行主线程      //执行【task1.Wait();task2.Wait();】可以实现相同功能      //调用Wait方法,可以阻塞任务,直至任务完成,效果等同于Thread.Join:      Task.WaitAll(new Task[]{ task1,task2});      Console.WriteLine("主线程执行完毕!");      Console.ReadKey();  }
  • 延续操作

让所有task执行完毕(或者任一task执行完毕)后,开始执行后续操作。WhenAny/WhenAll方法了,这些方法执行完成返回一个task实例。 task.WhenAll(Task[] tasks) 表示所有的task都执行完毕后再去执行后续的操作, task.WhenAny(Task[] tasks) 表示任一task执行完毕后就开始执行后续操作。

static void Main(string[] args){    Task task1 = new Task(() => {        Thread.Sleep(500);        Console.WriteLine("线程1执行完毕!");    });    task1.Start();    Task task2 = new Task(() => {        Thread.Sleep(1000);        Console.WriteLine("线程2执行完毕!");    });    task2.Start();    //通过TaskFactroy实现    Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>                                 {                                     Thread.Sleep(100);                                     Console.WriteLine("执行后续操作");                                 });    Console.WriteLine("主线程执行完毕!");    Console.ReadKey();}
static void Main(string[] args){    Task task1 = new Task(() => {        Thread.Sleep(500);        Console.WriteLine("线程1执行完毕!");    });    task1.Start();    Task task2 = new Task(() => {        Thread.Sleep(1000);        Console.WriteLine("线程2执行完毕!");    });    task2.Start();    //task1,task2执行完了后执行后续操作    Task.WhenAll(task1, task2).ContinueWith((t) => {        Thread.Sleep(100);        Console.WriteLine("执行后续操作完毕!");    });    Console.WriteLine("主线程执行完毕!");    Console.ReadKey();}
.4. Task 取消
static void Main(string[] args){    bool isStop = false;    int index = 0;    //开启一个线程执行任务    Thread th1 = new Thread(() =>                            {                                while (!isStop)                                {                                    Thread.Sleep(1000);                                    Console.WriteLine($"第{++index}次执行,线程运行中...");                                }                            });    th1.Start();    //五秒后取消任务执行    Thread.Sleep(5000);    isStop = true;    Console.ReadKey();}
  • 使用CancellationTokenSource
static void Main(string[] args){    CancellationTokenSource source = new CancellationTokenSource();    int index = 0;    //开启一个task执行任务    Task task1 = new Task(() =>                          {                              while (!source.IsCancellationRequested)                              {                                  Thread.Sleep(1000);                                  Console.WriteLine($"第{++index}次执行,线程运行中...");                              }                          });    task1.Start();    //五秒后取消任务执行    Thread.Sleep(5000);    //source.Cancel()方法请求取消任务,IsCancellationRequested会变成true    source.Cancel();    Console.ReadKey();}
 static void Main(string[] args) {     CancellationTokenSource source = new CancellationTokenSource();     //注册任务取消的事件     source.Token.Register(() =>                           {                               Console.WriteLine("任务被取消后执行xx操作!");                           });     int index = 0;     //开启一个task执行任务     Task task1 = new Task(() =>                           {                               while (!source.IsCancellationRequested)                               {                                   Thread.Sleep(1000);                                   Console.WriteLine($"第{++index}次执行,线程运行中...");                               }                           });     task1.Start();     //延时取消,效果等同于Thread.Sleep(5000);source.Cancel();     source.CancelAfter(5000);     Console.ReadKey(); }
  • 跨线程
public partial class Form1 : Form{    public Form1()    {        InitializeComponent();    }    private void mySetValueBtn_Click(object sender, EventArgs e)    {        Task.Run(() =>                 {                     Action<int> setValue = (i) => { myTxtbox.Text = i.ToString(); };                     for (int i = 0; i < 1000000; i++)                     {                         myTxtbox.Invoke(setValue,i);                     }                 });    }}
.5. 返回值

Task<TResult>允许任务返回一个值。调用Task.Run,传入一个Func<TResult>代理(或者兼容的Lambda表达式),代替Action,就可以获得一个Task:

Task<int> task = Task.Run (() => { Console.WriteLine ("Foo"); return 3; });int result = task.Result;      // Blocks if not already finishedConsole.WriteLine (result);    // 3

下面的例子创建一个任务,它使用LINQ就按前3百万个整数(从2开始)中的素数个数:

Task<int> primeNumberTask = Task.Run(() =>                                     Enumerable.Range(2, 3000000).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));Console.WriteLine("Task running...");Console.WriteLine("The answer is " + primeNumberTask.Result);
.6. 延迟

Task.DelayThread.Sleep的异步版本

Task.Delay(5000).GetAwaiter().OnCompleted(()=>Console.WriteLine(42));

或者

Task.Delay(5000).ContinueWith(ant => Console.WriteLine(42));
2. 线程相关
1. 创建线程
static void Main(string[] args){    Thread t = new Thread(PrintNumbers);    t.Start();//线程开始执行    PrintNumbers();    Console.ReadKey();}static void PrintNumbers(){    Console.WriteLine("Starting...");    for (int i = 1; i < 10; i++)    {        Console.WriteLine(i);    }}
2. 暂停线程
class Program{    static void Main(string[] args)    {        Thread t = new Thread(PrintNumbersWithDelay);        t.Start();        PrintNumbers();        Console.ReadKey();    }    static void PrintNumbers()    {        Console.WriteLine("Starting...");        for (int i = 1; i < 10; i++)        {            Console.WriteLine(i);        }    }    static void PrintNumbersWithDelay()    {        Console.WriteLine("Starting...");        for (int i = 1; i < 10; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(2));//暂停2S            Console.WriteLine(i);        }    }}
3. 线程等待
class Program{    static void Main(string[] args)    {        Console.WriteLine("Starting program...");        Thread t = new Thread(PrintNumbersWithDelay);        t.Start();        t.Join();        Console.WriteLine("Thread completed");    }    static void PrintNumbersWithDelay()    {        Console.WriteLine("Starting...");        for (int i = 1; i < 10; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(2));            Console.WriteLine(i);        }    }}
4. 终止线程

并不推荐使用,Abort方法来关闭线程。可优先使用一些其他方法,比如提供一个CancellationToken方法来,取消线程的执行。

class Program{    static void Main(string[] args)    {        Console.WriteLine("Starting program...");        Thread t = new Thread(PrintNumbersWithDelay);        t.Start();        Thread.Sleep(TimeSpan.FromSeconds(6));        t.Abort();        Console.WriteLine("A thread has been aborted");    }    static void PrintNumbersWithDelay()    {        Console.WriteLine("Starting...");        for (int i = 1; i < 10; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(2));            Console.WriteLine(i);        }    }}
5.监控线程状态
class Program{    static void Main(string[] args)    {        Console.WriteLine("Starting program...");        Thread t = new Thread(PrintNumbersWithStatus);        Thread t2 = new Thread(DoNothing);        Console.WriteLine(t.ThreadState.ToString());        t2.Start();        t.Start();        for (int i = 1; i < 30; i++)        {            Console.WriteLine(t.ThreadState.ToString());        }        Thread.Sleep(TimeSpan.FromSeconds(6));        t.Abort();        Console.WriteLine("A thread has been aborted");        Console.WriteLine(t.ThreadState.ToString());        Console.WriteLine(t2.ThreadState.ToString());        Console.ReadKey();    }    static void DoNothing()    {        Thread.Sleep(TimeSpan.FromSeconds(2));    }    static void PrintNumbersWithStatus()    {        Console.WriteLine("Starting...");        Console.WriteLine(Thread.CurrentThread.ThreadState.ToString());        for (int i = 1; i < 10; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(2));            Console.WriteLine(i);        }    }}
7. 线程传参
class Program{    static void Main(string[] args)    {        var sample = new ThreadSample(10);        var threadOne = new Thread(sample.CountNumbers);        threadOne.Name = "ThreadOne";        threadOne.Start();        threadOne.Join();        Console.WriteLine("--------------------------");        var threadTwo = new Thread(Count);        threadTwo.Name = "ThreadTwo";        threadTwo.Start(8);        threadTwo.Join();        Console.WriteLine("--------------------------");        var threadThree = new Thread(() => CountNumbers(12));        threadThree.Name = "ThreadThree";        threadThree.Start();        threadThree.Join();        Console.WriteLine("--------------------------");        int i = 10;        var threadFour = new Thread(() => PrintNumber(i));        i = 20;        var threadFive = new Thread(() => PrintNumber(i));        threadFour.Start();         threadFive.Start();    }    static void Count(object iterations)    {        CountNumbers((int)iterations);    }    static void CountNumbers(int iterations)    {        for (int i = 1; i <= iterations; i++)        {            Thread.Sleep(TimeSpan.FromSeconds(0.5));            Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);        }    }    static void PrintNumber(int number)    {        Console.WriteLine(number);    }    class ThreadSample    {        private readonly int _iterations;        public ThreadSample(int iterations)        {            _iterations = iterations;        }        public void CountNumbers()        {            for (int i = 1; i <= _iterations; i++)            {                Thread.Sleep(TimeSpan.FromSeconds(0.5));                Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i);            }        }    }}
.2. APM->Task

APM(Asynchronous Programming Model)是.Net 旧版本中广泛使用的异步编程模型。使用了 APM的异步方法会返回一个 IAsyncResult 对象,这个对象有一个重要的属性 AsyncWaitHandle,他是一个用来等待异步任务执行结束的一个同步信号。 方法名字以 BeginXXX 开头,返回类型为 IAsyncResult,调用结束后需要EndXXX。

FileStream fs = File.OpenRead("d:/1.txt");byte[] buffer = new byte[16];IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);aResult.AsyncWaitHandle.WaitOne();//等待任务执行结束MessageBox.Show(Encoding.UTF8.GetString(buffer));fs.EndRead(aResult);
/// <summary>/// 异步计算接口/// </summary>/// <typeparam name="T"></typeparam>public interface ICalculator<T>{    IAsyncResult BeginAdd(T x, T y, AsyncCallback asyncCallback, Object obj);    T EndAdd(IAsyncResult ar);}/// <summary>/// 异步计算接口实现类/// </summary>public class Calculator : ICalculator<double>{    public IAsyncResult BeginAdd(double x, double y, AsyncCallback asyncCallback, Object obj)    {        return CalculatorAsyncResult<double>.CreateCalculatorAsyncResult(delegate { return Add(x, y); }, asyncCallback, obj);    }    public double EndAdd(IAsyncResult ar)    {        var calculatorAsyncResult = (CalculatorAsyncResult<double>)(ar);        Console.WriteLine("EndAdd Wait() start");        calculatorAsyncResult.Wait();        Console.WriteLine("EndAdd Wait() Invoke");        return calculatorAsyncResult.CalulatorResult;    }    /// <summary>    /// 计算方法    /// </summary>    /// <param name="x"></param>    /// <param name="y"></param>    /// <returns></returns>    protected double Add(double x, double y)    {        Console.WriteLine("Async thread(id={0}) begins.\n", Thread.CurrentThread.ManagedThreadId);        Thread.Sleep(3000);        var r = x + y;        Console.WriteLine("Async thread(id={0}) ends.\n", Thread.CurrentThread.ManagedThreadId);        return r;    }}
1 /// <summary> 2         /// 异步 3         /// </summary> 4         /// <param name="sender"></param> 5         /// <param name="e"></param> 6         private void button2_Click(object sender, EventArgs e) 7         {11             var url = this.textBox1.Text.Trim();12             var request = HttpWebRequest.Create(url);13             request.BeginGetResponse(AsyncCallbackImpl, request);//BeginGetResponse,发起异步请求14         }15 16         /// <summary>17         /// 回调18         /// </summary>19         /// <param name="ar"></param>20         public void AsyncCallbackImpl(IAsyncResult ar)21         {22             HttpWebRequest request = ar.AsyncState as HttpWebRequest;23             var response = request.EndGetResponse(ar);//EndGetResponse,异步请求完成24             var stream = response.GetResponseStream();25             StringBuilder sb = new StringBuilder();26             sb.AppendLine("当前线程Id:" + Thread.CurrentThread.ManagedThreadId);27             using (StreamReader reader = new StreamReader(stream))28             {29                 var content = reader.ReadLine();30                 sb.AppendLine(content);31                 this.richTextBox1.Text = sb.ToString();32             }33         }34
.3. EAP ->Task

基于事件的异步模式是.net 2.0提出的,实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法对应的Completed事件,并且这些类都支持异步方法的取消、进度报告和报告结果

EAP模式类似于 Ajax 中的XmlHttpRequest,send 之后并不是处理完成了,而是在 onreadystatechange 事件中再通知处理完成

优点是简单,缺点是当实现复杂的业务的时候很麻烦,比如下载 A 成功后再下载 b,如果下载 b成功再下载 c,否则就下载 d。

当调用基于事件的EAP模式的类的XXXAsync方法时,就开始了一个异步操作,并且基于事件的EAP模式是基于APM模式之上的,而APM又是建立在委托之上的。

BackgroundWorker worker = new BackgroundWorker();worker.DoWork += Worker_DoWork;worker.RunWorkerCompleted += Worker_RunWorkerCompleted;worker.RunWorkerAsync(null);private static void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){    Console.WriteLine("dowork completed");}private static void Worker_DoWork(object sender, DoWorkEventArgs e){    Console.WriteLine("dowork");}
WebClient wc = new WebClient();wc.DownloadStringCompleted += Wc_DownloadStringCompleted;wc.DownloadStringAsync(new Uri("http://www.baidu.com"));private void Wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e){  MessageBox.Show(e.Result);}
.4. TAP->Task

.net 4.0为我们带来了Task的异步,我们有以下三种方法创建Task。

1,Task.Factory.StartNew(),比较常用。

2,Task t1 = new Task(() => { Console.WriteLine(“t1 start”); }); t1.Start();

3,Task.Run(),是.net 4.5中增加的。

4,Task.FromResult(),如果结果是已计算,就可以使用这种方法来创建任务。

static void Main(string[] args){    Console.WriteLine("start");     Task task1= Task.Run(() =>    {        Thread.Sleep(2000);         Console.WriteLine("task1");    });    Task task2=Task.Run(() =>    {        Thread.Sleep(3000);         Console.WriteLine("task2");    });    Task.WaitAll(task1,task2);    Console.WriteLine($"task1 task2已经执行完成");    Task task3=Task.Run(() =>    {        Thread.Sleep(5000);         Console.WriteLine("task3");    });    Console.WriteLine("end");     Console.ReadLine();}
.5. TaskScheduler
.1. ThreadPoolTaskScheduler
protected internal override void QueueTask(Task task){    if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)    {        new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork)        {            IsBackground = true        }.Start(task);        return;    }    bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) > TaskCreationOptions.None;    ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);}
.2. SynchronizationContextTaskScheduler

这是一个同步上下文的taskscheduler,原理就是把繁重的耗时工作丢给ThreadPool,然后将更新UI的操作丢给 UI线程的队列中

private void button1_Click(object sender, EventArgs e){    Task task = Task.Factory.StartNew(() =>                                      {                                          //复杂操作,等待10s                                          Thread.Sleep(10000);                                      }).ContinueWith((t) =>                                                      {                                                          button1.Text = "hello world";                                                      }, TaskScheduler.FromCurrentSynchronizationContext());}

5. async/await

.1. async/await使用

await 后面跟上一个异步操作Task,当程序执行到此行代码时,由于有await的修饰,会等待异步操作Task执行,Task执行完成后才会执行此方法里await这一行后面的代码.

  • async用来声明一个异步方法,await是一个上下文关键字(跟代码上下文相关),必须和async配套使用才会生效,await用来声明异步方法中一个可能会产生阻塞的操作(方法)。
  • 当将方法用async标识时且返回值为void或者Task或者Task,此时该方法会在当前线程中一直同步执行。用async标识方法并不会影响方法运行完成是否是同步或者异步,相反,它能够将方法划分成多块,有可能有些在异步中运行,以至于这些方法是异步完成的,而划分异步和同步方法的边界就是使用await关键字。也就是说如果在方法中未用到await关键字时则该方法就是一整块没有所谓的划分,会在同步中运行,在同步中完成。
  • C#编译器一旦遇到以async声明的方法(即异步方法)时会在这个方法中尝试寻找await关键字,如果找到以await关键字声明的方法,就会自动生成一些代码,这些代码负责启动后台线程,尝试找到空闲的CPU内核运行以await声明的方法(即以异步方式运行),完成这一切后调用者线程从异步方法返回。后台线程在运行完以await声明的方法后会结束,此时await关键字生成的代码还负责提取方法的返回结果,所以干活的都是await,async实际上只是提醒编译器"你看到有async关键字的方法时进去方法内部是不是有个await关键字,有的话就干活"。
  • C#中可以用async标识方法,表示这个方法是异步的。异步方法的返回值必须是voidTask或者Task<T>
  • Method 1和Method 2不相互依赖, 并且俩个方法一起执行
class Program{      static void Main(string[] args)    {          Method1();        Method2();        Console.ReadKey();    }      public static async Task Method1()    {          await Task.Run(() =>                       {                             for (int i = 0; i < 100; i++)                           {                                 Console.WriteLine(" Method 1");                             }                         });      }      public static void Method2()    {          for (int i = 0; i < 25; i++)        {              Console.WriteLine(" Method 2");          }      }  }  
  • Method 1将总长度作为整数值返回,我们在Method 3中以长度的形式传递一个参数,它来自Method 1,在控制台应用程序的Main方法中,因为不能使用async关键字而不能使用await 关键字
class Program{      static void Main(string[] args)    {          callMethod();        Console.ReadKey();    }      public static async void callMethod()    {          Task<int> task = Method1();        Method2();        int count = await task;        Method3(count);    }      public static async Task<int> Method1()    {          int count = 0;        await Task.Run(() =>                       {                             for (int i = 0; i < 100; i++)                           {                                 Console.WriteLine(" Method 1");                                 count += 1;                           }                         });          return count;    }      public static void Method2()    {          for (int i = 0; i < 25; i++)        {              Console.WriteLine(" Method 2");          }      }      public static void Method3(int count)    {          Console.WriteLine("Total count is " + count);    }  }  
  • demo
class Program{      static void Main()    {          Task task = new Task(CallMethod);        task.Start();        task.Wait();        Console.ReadLine();    }      static async void CallMethod()    {          string filePath = "E:\\sampleFile.txt";          Task<int> task = ReadFile(filePath);        Console.WriteLine(" Other Work 1");          Console.WriteLine(" Other Work 2");          Console.WriteLine(" Other Work 3");          int length = await task;        Console.WriteLine(" Total length: " + length);        Console.WriteLine(" After work 1");          Console.WriteLine(" After work 2");      }      static async Task<int> ReadFile(string file)    {          int length = 0;        Console.WriteLine(" File reading is stating");          using (StreamReader reader = new StreamReader(file))        {              // Reads all characters from the current position to the end of the stream asynchronously               // and returns them as one string.               string s = await reader.ReadToEndAsync();            length = s.Length;        }          Console.WriteLine(" File reading is completed");          return length;    }  }  

.2. lambda 表达式

6. 并发集合

线程安全:多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的.

线程不安全:不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
举例:购票系统有1000张票。A线程和B线程同时抢票,有时候会抢到同一张票。这是就是线程不安全。两个线程可能同时抢一个数据。这时候就要用到锁了.

.1. ConcurrentQueue

ConcurrentQueue队列是一个高效的线程安全的队列,是.Net Framework 4.0,System.Collections.Concurrent 命名空间下的一个数据结构.

.2. ConcurrentDictionary
.3. ConcurrentStack
.4. ConcurrentBag

ConcurrentBag是一个专门的并发包裹,在连接池(多线程数据交互)的实现上具有比LinkedBlockingQueue和LinkedTransferQueue更优越的性能。
ConcurrentBag通过拆分 CopyOnWriteArrayList、ThreadLocal和SynchronousQueue
进行并发数据交互。

  • CopyOnWriteArrayList:负责存放ConcurrentBag中全部用于出借的资源
  • ThreadLocal:用于加速线程本地化资源访问
  • SynchronousQueue:用于存在资源等待线程时的第一手资源交接
  • ConcurrentBag中全部的资源均只能通过add方法进行添加,只能通过remove方法进行移出。
.5. BlockingCollection

7. PLINQ

任务并行库 (TPL) 是 System.ThreadingSystem.Threading.Tasks 空间中的一组公共类型和 API。 TPL 的目的是通过简化将并行和并发添加到应用程序的过程来提高开发人员的工作效率。 Parallel类提供数据和任务的并行性;

.1. 数据并行

数据并行 指的是对源集合或数组的元素同时(即,并行)执行相同操作的场景。 在数据并行操作中,对源集合进行分区,以便多个线程能够同时在不同的网段上操作。

.1. Foreach
// Sequential versionforeach (var item in sourceCollection){    Process(item);}// Parallel equivalentParallel.ForEach(sourceCollection, item => Process(item));//自定义数组, 可以解决ForEach中参数传递问题List<Kinectenv> kinectenvs = convertFromeEnv(e);_ = Parallel.ForEach(kinectenvs, (pair) =>                     {                         HandleSingSkeleton(pair.kinectId,pair.skeleton,pair.image, rtime);                     });
.2. For
namespace ConsoleApp1{    class Program    {        static void Main(string[] args)        {            int[] nums = new int[] { , , , , , , , , , , ,  };            Parallel.For(, nums.Length, (i) =>            {                Console.WriteLine("针对数组索引{0}对应的那个元素{1}的一些工作代码……ThreadId={2}", i, nums[i], Thread.CurrentThread.ManagedThreadId);            });            Console.ReadKey();        }    }}
.3. 退出
static void Main(string[] args){    ConcurrentBag<int> bag = new ConcurrentBag<int>();    Parallel.For(, , (i, state) =>                 {                     if (bag.Count == )                     {                         //state.Break();                         state.Stop();                         return;                     }                     bag.Add(i);                 });    Console.WriteLine("当前集合有{0}个元素。", bag.Count);}
.4. 异常处理
private static void ProcessDataInParallel(byte[] data){    // Use ConcurrentQueue to enable safe enqueueing from multiple threads.    var exceptions = new ConcurrentQueue<Exception>();    // Execute the complete loop and capture all exceptions.    Parallel.ForEach(data, d =>                     {                         try                         {                             // Cause a few exceptions, but not too many.                             if (d < 3)                                 throw new ArgumentException($"Value is {d}. Value must be greater than or equal to 3.");                             else                                 Console.Write(d + " ");                         }                         // Store the exception and continue with the loop.                         catch (Exception e)                         {                             exceptions.Enqueue(e);                         }                     });    Console.WriteLine();    // Throw the exceptions here after the loop completes.    if (exceptions.Count > 0) throw new AggregateException(exceptions);}
.2. 任务并行

任务并行库 (TPL) 以“任务”的概念为基础,后者表示异步操作。 在某些方面,任务类似于线程或 ThreadPool 工作项,但是抽象级别更高。 术语“任务并行”是指一个或多个独立的任务同时运行。

.1. Invoke 隐式创建

这个函数的功能和Task有些相似,就是并发执行一系列任务,然后等待所有完成。和Task比起来,省略了Task.WaitAll这一步,自然也缺少了Task的相关管理功能。它有两种形式:

  • Parallel.Invoke( params Action[] actions);
  • Parallel.Invoke(Action[] actions,TaskManager manager,TaskCreationOptions options);
namespace ConsoleApp1{    class Program    {        static void Main(string[] args)        {            var actions = new Action[]            {                () => ActionTest("test 1"),                () => ActionTest("test 2"),                () => ActionTest("test 3"),                () => ActionTest("test 4")            };             Console.WriteLine("Parallel.Invoke 1 Test");            Parallel.Invoke(actions);             Console.WriteLine("结束!");        }         static void ActionTest(object value)        {            Console.WriteLine(">>> thread:{0}, value:{1}",            Thread.CurrentThread.ManagedThreadId, value);        }    }}
.2. Task 显示创建
using System;using System.Threading;using System.Threading.Tasks;class CustomData{   public long CreationTime;   public int Name;   public int ThreadNum;}public class Example{   public static void Main()   {      Task[] taskArray = new Task[10];      for (int i = 0; i < taskArray.Length; i++) {         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {                                                  CustomData data = obj as CustomData;                                                  if (data == null)                                                     return;                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;                                               },                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );      }      Task.WaitAll(taskArray);      foreach (var task in taskArray) {         var data = task.AsyncState as CustomData;         if (data != null)            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",                              data.Name, data.CreationTime, data.ThreadNum);      }   }}
.3. 任务延续

使用 Task.ContinueWithTask.ContinueWith 方法,可以指定要在先行任务完成时启动的任务。 延续任务的委托已传递了对先行任务的引用,因此它可以检查先行任务的状态,并通过检索 Task.Result 属性的值将先行任务的输出用作延续任务的输入

在下面的示例中,getData 任务通过调用 TaskFactory.StartNew(Func) 方法来启动。 当 processData 完成时,getData 任务自动启动,当 displayData 完成时,processData 启动。 getData 产生一个整数数组,通过 processData 任务的 getData 属性,Task.Result 任务可访问该数组。 processData 任务处理该数组并返回结果,结果的类型从传递到 Task.ContinueWith(Func,TNewResult>) 方法的 Lambda 表达式的返回类型推断而来。 displayData 完成时,processData 任务自动执行,而 Tuple 任务可通过 processData 任务的 displayData 属性访问由 processData lambda 表达式返回的 Task.Result 对象。 displayData 任务采用 processData 任务的结果,继而得出自己的结果,其类型以相似方式推断而来,且可由程序中的 Result 属性使用。

using System;using System.Threading.Tasks;public class Example{    public static void Main()    {        var getData = Task.Factory.StartNew(() => {            Random rnd = new Random();            int[] values = new int[100];            for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)                values[ctr] = rnd.Next();            return values;        } );        var processData = getData.ContinueWith((x) => {            int n = x.Result.Length;            long sum = 0;            double mean;            for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)                sum += x.Result[ctr];            mean = sum / (double) n;            return Tuple.Create(n, sum, mean);        } );        var displayData = processData.ContinueWith((x) => {            return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",                                 x.Result.Item1, x.Result.Item2,                                 x.Result.Item3);        } );        Console.WriteLine(displayData.Result);    }}// The example displays output similar to the following:// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
using System;using System.Threading.Tasks;public class Example{    public static void Main()    {        var displayData = Task.Factory.StartNew(() => {            Random rnd = new Random();            int[] values = new int[100];            for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)                values[ctr] = rnd.Next();            return values;        } ).            ContinueWith((x) => {                int n = x.Result.Length;                long sum = 0;                double mean;                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)                    sum += x.Result[ctr];                mean = sum / (double) n;                return Tuple.Create(n, sum, mean);            } ).            ContinueWith((x) => {                return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",                                     x.Result.Item1, x.Result.Item2,                                     x.Result.Item3);            } );        Console.WriteLine(displayData.Result);    }}// The example displays output similar to the following://    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
.4. 创建分离的子任务

父任务不会等待分离子任务完成。

var outer = Task.Factory.StartNew(() =>{    Console.WriteLine("Outer task beginning.");    var child = Task.Factory.StartNew(() =>    {        Thread.SpinWait(5000000);        Console.WriteLine("Detached task completed.");    });});outer.Wait();Console.WriteLine("Outer task completed.");// The example displays the following output://    Outer task beginning.//    Outer task completed.//    Detached task completed.
.5. 创建子任务

如果任务中运行的用户代码在创建任务时指定了 AttachedToParent 选项,新任务就称为父任务的附加子任务。

using System;using System.Threading;using System.Threading.Tasks;public class Example{    public static void Main()    {        var parent = Task.Factory.StartNew(() => {            Console.WriteLine("Parent task beginning.");            for (int ctr = 0; ctr < 10; ctr++) {                int taskNo = ctr;                Task.Factory.StartNew((x) => {                    Thread.SpinWait(5000000);                    Console.WriteLine("Attached child #{0} completed.",                                      x);                },taskNo, TaskCreationOptions.AttachedToParent);            }        });        parent.Wait();   // 等待子任务的完成        Console.WriteLine("Parent task completed.");    }}
  • 创建多个子任务,并保持关联
static void Main(string[] args){    var parentTask = new Task<int[]>(() =>                                     {                                         //开启多个子任务                                         var results = new int[2];                                         //创建子任务,并将子任务的值赋给results变量,并通过TaskCreationOptions.AttachedToParent,将其关联到父任务,如果不指定,该任务将独立于父任务单独执行                                         //这里有个奇怪的问题,只能使用new Task的方式去创建关联到父任务的子任务,因为Task.Run没有提供这个方法,可以通过扩展方法解决这个问题                                         new Task(() => results[0] = ChildThreadOne(), TaskCreationOptions.AttachedToParent).Start();                                         new Task(() => results[1] = ChildThreadTwo(), TaskCreationOptions.AttachedToParent).Start();                                         return results;                                     });    parentTask.Start();    parentTask.ContinueWith(x =>                            {                                Console.WriteLine("当父任务执行完毕时,CLR会唤起一个新线程,将父任务的返回值(子任务的返回值)输出,所以这里不会有任何的线程发生阻塞");                                foreach (var re in parentTask.Result)                                {                                    Console.WriteLine("子任务的返回值分别为:{0}", re);                                }                            });    Console.WriteLine("主线程不会阻塞,它会继续执行");    Console.ReadKey();//必须加这行代码,因为Task时线程池线程,属于后台线程}/// <summary>/// 子任务一/// </summary>static int ChildThreadOne(){    Thread.Sleep(2000);//模拟长时间计算操作    Console.WriteLine("子任务一完成了计算任务,并返回值:{0}", 6);    return 6;}/// <summary>/// 子任务一/// </summary>static int ChildThreadTwo(){    Thread.Sleep(2000);//模拟长时间计算操作    Console.WriteLine("子任务二完成了计算任务,并返回值:{0}", 6);    return 6;}
.6. 等待任务的完成
Task[] tasks = new Task[3]{    Task.Factory.StartNew(() => MethodA()),    Task.Factory.StartNew(() => MethodB()),    Task.Factory.StartNew(() => MethodC())};//Block until all tasks complete.Task.WaitAll(tasks);// Continue on this thread...   ?? 这里是指代 主线程不会等待 task线程吗?  是的

8. Rx

Rx最显著的特性是使用可观察集合(Observable Collection)来达到集成异步(composing asynchronous)基于事件(event-based)的编程的效果。Rx有一些几个特性。 类似UI中的触发控件

  • 组合(Composing): Reactive Extension的首要目标之一就是将多种异步操作组合起来是的代码更加简单。要做到这一点,数据流必须定义清楚,这样代码就很清晰集中,使得异步操作代码异步处理代码不会充斥整个应用程序。
  • 异步(Asynchronous): 虽然Rx不仅仅能处理异步操作,但是使用Rx,大大简化了异步操作的实现,并且代码容易理解进而容易维护。
  • 基于事件(Event-based): Rx简化了传统的异步编程方式
  • 可观察集合(Observable collections): Obervable Collection是Rx的核心,它是一种集合,集合的元素在第一次访问的时候肯能还没有填充。它对与Rx的重要性类始于enumerable集合对LINQ的重要性。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Reactive.Linq;using System.IO;namespace RxDemo{    class Program    {        static void Main(string[] args)        {            //使用Range方法返回Observable集合            IObservable<Int32> input = Observable.Range(1, 15);            input.Where(i => i % 2 == 0).Subscribe(x => Console.Write("{0} ", x));            Console.WriteLine();            //使用Array返回Observabale集合            var myArray = new[] { 1, 3, 5, 7, 9 };            IObservable<Int32> varmyObservable = myArray.ToObservable();            varmyObservable.Subscribe(x => Console.WriteLine("Integer:{0}", x));            Console.WriteLine();            //Take操作符,用来指定获取集合中的前几项            var take = new[] { 1, 2, 3, 4, 5, 4, 3, 2, 1 }.ToObservable();            take.Take(5).Select(x => x * 10).Subscribe(x => Console.WriteLine(x));            Console.WriteLine();            //Skip操作符表示跳过集合中的n条记录。            var skip = new[] { 1, 2, 3, 4, 5, 4, 3, 2, 1 }.ToObservable();            skip.Skip(6).Select(x => x * 10).Subscribe(x => Console.WriteLine(x));            Console.WriteLine();            //Distinct操作符用来去除集合中的非重复数据。            var distinct = new[] { 1, 2, 3, 4, 5, 4, 3, 2, 1 }.ToObservable();            distinct.Distinct().Select(x => x * 10).Subscribe(x => Console.WriteLine(x));            //Rx也需要释放资源            Console.WriteLine();            var ObservableStrings = Observable.Using<char, StreamReader>(                () => new StreamReader(new FileStream("randomtext.txt", FileMode.Open)),                streamReader => (streamReader.ReadToEnd().Select(str => str)).ToObservable()                );            ObservableStrings.Subscribe(Console.Write);            Console.WriteLine();            //在Rx中Zip是将两个Observable对象合并为一个新的Observable对象。            var numberCitys = varmyObservable.Zip(input, (range, array) => range + ":" + array);            numberCitys.Subscribe(Console.WriteLine);            Console.ReadKey();        }    }}

9. 并行/顺序执行

  • **串行:**A和B两个任务运行在一个CPU线程上,在A任务执行完之前不可以执行B。即,在整个程序的运行过程中,仅存在一个运行上下文,即一个调用栈一个堆。程序会按顺序执行每个指令。
  • **并发:**并发指多个线程在宏观(相对于较长的时间区间而言)上表现为同时执行,而实际上是轮流穿插着执行,并发的实质是一个物理CPU在若干道程序之间多路复用,其目的是提高有限物理资源的运行效率。 并发与并行串行并不是互斥的概念,如果是在一个CPU线程上启用并发,那么自然就还是串行的,而如果在多个线程上启用并发,那么程序的执行就可以是既并发又并行的。
  • **并行:**并行性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。比如,A和B两个任务可以同时运行在不同的CPU线程上,效率较高,但受限于CPU线程数,如果任务数量超过了CPU线程数,那么每个线程上的任务仍然是顺序执行的。

public class Main {    //判断是否为质数    private static boolean isPrime(int n) {        if(n < 2) return false;        if(n == 2) return true;        if(n%2==0) return false;        for(int i = 3; i < n; i += 2)            if(n%i == 0) return false;        return true;    }    //串行计算private static void serial() {        long time1 = System.currentTimeMillis(), time2,time3;        long count = 0;        for(int i=1;i<=100000;++i){            if(isPrime(i)) count+=i;        }        time2=System.currentTimeMillis();        System.out.println("1-100000之间质数和为"+count+" 耗时:"+(time2- time1) + "毫秒");        count = 0;        for(int i=100001;i<=200000;++i){            if(isPrime(i))                count+=i;        }        time3 = System.currentTimeMillis();        System.out.println("100001-200000之间质数和为"+count+" 耗时:"+(time3 - time2) + "毫秒");        System.out.println("总耗时:"+ (time3 - time1) + "毫秒");    }    //主函数    public static void main(String[] args) {        serial();    }}
public class Main{    private static boolean isPrime(int n) {        if(n < 2) return false;        if(n == 2) return true;        if(n%2==0) return false;        for(int i = 3; i < n; i += 2)            if(n%i == 0) return false;        return true;    }    public static void main(String[] args) {        serialConcurrency();    }    private static void serialConcurrency() {        long time = System.currentTimeMillis();        //任务切换标识,1代表A任务,2代表B任务        int task = 1;        //计数器        long count1 = 0, count2 = 0;        int i=1,j=100001;        while (true)        {            if(task == 1 && i++<=100000) {                if(isPrime(i)) count1+=i;                task = 2;            }            else if(task == 2 && j++<=200000) {                if(isPrime(j)) count2+=j;                task = 1;            }            else{                break;            }        }        System.out.println("1-100000之间质数和为"+count1);        System.out.println("100001-200000之间质数和为"+count2);        System.out.println("总耗时:"+(System.currentTimeMillis() - time) + "毫秒");    }}
public class Main {    public static boolean isPrime(int n) {        if(n < 2) return false;        if(n == 2) return true;        if(n%2==0) return false;        for(int i = 3; i < n; i += 2)            if(n%i == 0) return false;        return true;    }    public static void main(String[] args) throws InterruptedException {        long time1 = System.currentTimeMillis(),time2;        Task task1 = new Task(1,100000);        Task task2 = new Task(100001,200000);        Thread thread1 = new Thread(task1);        Thread thread2 = new Thread(task2);        thread1.start();        thread2.start();        while (thread1.isAlive() || thread2.isAlive()){            Thread.sleep(1);        }        time2 = System.currentTimeMillis();        System.out.println("总耗时:"+(time2 - time1)+"毫秒");    }}class Task implements Runnable{    private int start;    private int end;    Task(int start, int end) {        this.start = start;        this.end = end;    }    public void run() {        long time = System.currentTimeMillis();        long count = 0;        for(int i=start;i<=end;++i){            if(Main.isPrime(i)) count+=i;        }        System.out.println(String.format("%d-%d之间质数和为%d,耗时:%d毫秒",start,end,count,(System.currentTimeMillis()- time)));    }}

10. 异步IO

.1. FileStream
.2. Http
.3. 数据库
Resource
  • https://cloud.tencent.com/developer/article/1793724
  • https://www.cnblogs.com/wyt007/p/9486752.html
  • https://www.cnblogs.com/jackson0714/p/5125808.html
  • https://www.huaweicloud.com/articles/cfc1249c1ea36c04c429703a9f150d52.html
  • https://www.shuzhiduo.com/A/gVdnPZaaJW/
  • https://www.jianshu.com/p/deae44fcc6b3
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路途…

点滴记录

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值