The .NET Asynchronous Programming Task Parallel Library

shanzm- 2020 Nian 2 Yue 16 Ri 00:45:04

1 Introduction

System.Threading.TasksThe type is known as Task Parallel Library (Task Parallel Library, TPL ).

System.Thread.Tasks Namespace is .NET Framework4.0 provided,

"TPL automatically using the CLR thread pool dynamically allocates work to applications in .TPL also available CPU processing partition, thread scheduling, state management and other low-level details of the operation. The end result is that you can maximize. NET application performance and avoid the complexity of the operation thread directly caused by "-" proficient in C # "




2.Parallel class


2.0 Parallel class Introduction

In System.Threading.Tasksa static class in the namespace: Parallel class

Parallel implements can be achieved for each data element of a set of parallel IEnumerable interface operation

One thing to note: The parallel operation will bring some cost, if the task itself can be completed quickly, or very few number of cycles, then the parallel processing speed might be slower than non-parallel processing.

Parallel class only has three methods: Parallel.For(), Parallel.ForEach()andParallel.Invoke()

But then, each of these methods has a lot of heavy-duty (F12 -> View Parallel define their own)


2.1 Parallel.For()

Use Parallel.For()may be made to each element of the array operating in parallel

Normal iterate index is performed in the order of, but parallel operation, the operation of each element of the array are not necessarily sequential operations by index

The Parallel.For (), the first parameter is the index of the cycle start (inclusive), the second parameter is the end loop index (no)

The Parallel.For () of the third parameter is a parameter delegate no return value, which is the index into the array parameter

In fact equivalent: for (int i = 0; i < length; i++)asynchronous version, but here the parallel operation, it is not executed in the order of elements in the array, the specific order of execution is not controllable .

Examples

static void Main(string[] args)
{
    int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    Console.WriteLine("------------常规,对数组进行循环遍历------------");
    Array.ForEach(intArray, n => Console.WriteLine($"当前操作的数组元素是{n}"));//注意这里的参数n是元素而不是索引
    
    Console.WriteLine("------------并行操作 对数组进行循环遍历------------");
    
    Parallel.For(0, intArray.Length, (i) => Console.WriteLine($"当前循环次数{i},当前操作的数组元素是{intArray[i]}"));
    Console.ReadKey();
}

The result: As can be seen, the sequence of operations of the elements of the array are not in accordance with the order of the index, but uncertain.

Parallel.For


2.2 Parallel.ForEach()

Parallel.ForEach()Element for generic enumerable object to be operated in parallel

In fact, the equivalent of: foreach (var item in collection)asynchronous version

Parallel.ForEach () have a lot of heavy-duty, showing a simple operation here

Parallel.ForEach first parameter () is enumerable object to be operated, the second parameter is a parameter value of no return delegate, the delegate parameters are elements of the set (not the index)

Examples

List<int> intList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Parallel.ForEach(intList, n => Console.WriteLine(n+100));
Console.ReadKey();


2.3 Parallel.Invoke()

Parallel.Invoke()A series of parallel arithmetic operations specified

Action delegate is a parameter array (note that only Action [], i.e., is not only an array of values ​​returned delegate)

Parallel.Invoke () the most common interfaces for concurrent requests

Example:

static void Main(string[] args)
{
     Action action1=() =>
     {
         for (int i = 0; i < 5; i++)
         {
             Console.WriteLine($"action-1-操作");
         }
     };

     Action action2 = () =>
     {
         for (int i = 0; i < 5; i++)
         {
             Console.WriteLine($"action-2-操作");
         }
     };
     //Parallel.Invoke(action1, action2);
     Action[] actions = { action1, action2 };
     Parallel.Invoke(actions);
     Console.ReadKey();
}

operation result:

Parallel.Invoke


2.4 added: thread-safe collection

Details can refer to Microsoft's online documentation

Multithreading on the same set of data simultaneously read and write operations, data may cause confusion

.NET4 introduction of the System.Collections.Concurrentnamespace that contains multiple threads safety data collection type.

Now the new project, as long as the additions and deletions to the data collection operation multithreading, you should use the Concurrent collections classes.

However, if only a set of multi-threaded read, the data set may be used generally, i.e. System.Collections.Generic namespace class.

.net thread safe there at some data set:

Types of description
BlockingCollection To achieve IProducerConsumerCollection provide restrictions and blocking of all types. For more information, see BlockingCollection overview .
ConcurrentDictionary Key to achieve thread-safe dictionary.
ConcurrentQueue FIFO (First In First Out) queue of thread-safe.
ConcurrentStack Thread safety realization LIFO (last in, first out) stack.
ConcurrentBag Thread unordered collection of elements of the security implementation.
IProducerConsumerCollection Type must implement in order to BlockingCollectioninterface to use.

A simple example: a set of data to add large amounts of data

List<int> list = new List<int>();
Parallel.For(0, 1000000, t => list.Add(t));

If used according to the above Parallel.For()to add data to the List of parallel,

It will error: "Index was outside the bounds of the array." Or "Please check the source array is less than the lower limit srcIndex length and length of the array.."

Even if there is no error, the data list is problematic (probably better than some number)

Of course, you can compensate by locking manner:

List<int> list = new List<int>();
object locker = new object();
Parallel.For(0, 1000000, t => { lock(locker) { list.Add(t); } });

Such a thread through the shackles of operation, absolutely is not necessary, you can use the thread-safe collection type, such as used here ConcurrentBag

ConcurrentBag<int> cBag = new ConcurrentBag<int>();
Parallel.For(0, 100000, t => cBag.Add(t));

Of course, because parallel operations, the data set is not inserted in accordance with the order 0-100000 (ordered into segments only).




3.Task class


3.0 Task Class Profile

System.Threading.TasksNamespace Task class, for asynchronous operation.

Task class can easily call in the secondary thread, it can be used as a simple alternative to asynchronous delegate.

同时在该命名空间还有一个泛型Task<TResul>类,TResult 表示异步操作执行完成后返回值的类型。

创建一个Task操作,只需要使用静态函数Task.Run()即可,

Task.Run()是一个.net framework4.5及以上定义的一个默认异步操作,

Task.Run()参数是委托,即需要异步执行的方法,

注意作为Task.Run()的参数的委托都是无参委托

若Task.Run()参数是无返回值的委托Action,则Task.Run()返回值是Task类型

若Task.Run()参数是有返回值的委托Func<TResult>,则Task.Run()返回值是Task<TResult>泛型

注意:若是低于.net4.5,则可以使用Task.Factory.StartNew(),和Task.Run()静态方法作用一样

总而言之,言而总之,show you code ,一切皆明了!


3.1 创建无返回值的Task任务

示例:无返回值的Task

static void Main(string[] args)
{
    //1.使用Task构造函数创建,必须显式的使用.Start()才能开始执行
    //Task task = new Task(() => { Thread.Sleep(10); Console.WriteLine("我是Task ,我结束了"); });
    //task.Start();

    //2.使用TaskFactory.StartNew(工厂创建) 方法
    //Task task = Task.Factory.StartNew(() => { Thread.Sleep(10); Console.WriteLine("我是Task ,我结束了"); });

    //3.使用Task.Run()
    Task task = Task.Run(() => { Thread.Sleep(10); Console.WriteLine("我是Task.Run ,我结束了"); });
    if (!task.IsCompleted)//task.IsCompleted判断当前的任务是否已完成
    {
        Console.WriteLine("当前的Task.Run()尚未执行完,但是因为异步,返回到调用函数,所以可以先执行后续的代码");
    }

    Console.WriteLine("当前Task.Run还没有完成,我们是在他之后的代码但是先执行了");

    task.Wait();//强行锁定线程,等待task完成
    Console.WriteLine("终于Task.Run完成了工作");
    Console.ReadKey();
}


3.2 创建有返回值的Task任务

若是Task任务有返回值,返回值类型为Task<T>,使用返回值的Result属性查询具体值

调试时注意查看,运行到 Console.WriteLine(task.Result)的时候,其中Task任务还是在执行Thread.Sleep(1000)

还没有出结果,我们希望的异步执行也没有发生,而是程序是在一直在等待,这是为什么呢?

是因为一但执行了task.Result,即使task任务还没有完成,主线程则停下等待,直到等待task.Result出结果

这种情况和异步委托中调用EndInvoke()是一样的:一旦运行EndInvoke,若是引用方法还没有完成,主线程则停止,直到引用函数运行结束。

所以可以这样理解:task.Result可以看作是一个未来结果(一定有结果但还在运算中)

示例:有返回值的Task

static void Main(string[] args)
{
    Console.WriteLine("SomeDoBeforeTask");
    Func<int> Do = () => { Thread.Sleep(1000); Console.WriteLine("Task.Run结束"); return 2; };
    Task<int> task = Task.Run(Do);
    Console.WriteLine(task.Status);//使用task.Status查看当前的Task的状态:当前的状态:WaitingToRun
    Console.WriteLine(task.Result);//使用task.result操作Task任务的返回值:返回值是:2
    Console.WriteLine(task.Status);//使用task.Status查看当前的Task的状态:当前的状态:RanToComplation
    Console.WriteLine("SomeDoAfterTask");
    Console.ReadKey();
}

运行结果:

说明
其中我们使用task.Result查看当前的task的状态,其中Task的状态(即其生命周期)只有三种:

  • Created(创建Task):注意只有Task task=new Task(...),此时的Task状态为Created,其他方式创建的Task跳过了Created状态
  • WaitingToRun(等待执行Task)
  • RanToComplation(Task完成)


3.3 为Task添加延续任务

Task任务是在后台执行的同时,主线程的继续执行后续程序

所以有时候需要在Task结束后,继续执行某个特定的任务,即为Task添加延续任务(也称接续工作

举一个简单的例子,

求解1-5000能求被3整除的个数,这个过程需要许多时间,我把它定义为一个Task.Run()

我们需要在求出结果后打印出结果,这里怎么操作呢?

若是直接使用task.Result则会阻塞主线程,一直等待运算出结果,这显然不是我们想要的

若是使用while(!task.IsComplation){//后续操作},你无法判断Task何时结束,而且一旦Task结束则会中断后续操作

这里就是需要为Task加上接续工作

这里你可以明白,接续本质和异步委托中的回调模式是一样的,回调方法就是接续工作


3.3.1使用task.ContinueWith()

task1.ContinueWith(...task2..)表示当task1结束后接着运行task2任务

注意这里我们使用Lambda表达式编写接续工作,接续工作是有一个参数的,参数是Task类型,即上一个Task

即第一个Task完成后自动启动下一个Task,实现Task的延续

注意:ContinueWith()的返回值亦是Task类型对象,即新创建的任务

可以为接续工作task2继续添加接续工作task3

示例5 :

  static void Main(string[] args)
  {
      Console.WriteLine("task执行前...");
      Task<int> task1 = Task.Run(() => Enumerable.Range(1, 5000).Count(n => (n % 3) == 0));
      Task task2 = task1.ContinueWith(t => Console.WriteLine($"当你看到这句话则task1结束了,1-5000中能被3整除的个数{t.Result}"));//这里的t就是task1
      Task task3 = task2.ContinueWith(t => Console.WriteLine($"当你看到这句话则task2也结束了"));
      Console.WriteLine($"task1及其接续工作正在执行中," + "\t\n" + "我们现在正在执行其他的后续代码");
      Console.ReadKey();
  }

运行结果:


3.3.2使用Awaiter

使用task.GetAwaiter()为相关的task创建一个等待者

示例:

    static void Main(string[] args)
    {
        Console.WriteLine("task执行前...");
        Task<int> task1 = Task.Run(() => Enumerable.Range(1, 5000).Count(n => (n % 3) == 0));
        var awaiter = task1.GetAwaiter();//创建一个awaiter对象
        //awaiter.OnCompleted(() => Console.WriteLine($"当你看到这句话则task1结束了,1-5000中能被3整除的个{task1.Result}"));
        awaiter.OnCompleted(() => Console.WriteLine($"当你看到这句话则task1结束了,1-5000中能被3整除的个{awaiter.GetResult()}"));
        Console.WriteLine($"task1及其接续工作正在执行中," + "\t\n" + "我们现在正在执行其他的后续代码");
        Console.ReadKey();
    }

运行效果同上。

3.3.3使用ContinueWith和Awaiter的区别:

ContinueWith会返回Task对象,它非常适合用于增加更多的接续工作,不过,如果Task出错,必须直接处理AggregateException。

使用task.GetAwaiter创建awaiter对象,是在.net4.5之后,其中C#5.0的异步功能就是使用这种方式。

使用awaiter也是可以使用task.Result直接的查看任务的结果,但是使用awaiter.GetResult()可以在Task出现异常的时候直接抛出,不会封装在AggregateException中。

3.4 Task.Delay

延时执行Task

3.4.1 使用Task.Delay()和ContinueWith实现延迟工作

其实就相当于实现Thread.Sleep()的异步版本

若是你使用Thread.Sleep(),则会程序一直在等待(即阻塞线程),直到等待结束才会运行后续的代码

而这里就相当于给给Thread.Sleep()一个加了接续工作,且这个接续工作是异步的。

即使用Task.Delay()不会阻塞主线程,主线程可以继续执行后续代码

示例:

    //新建异步任务,30毫秒秒后执行
    Task.Delay(30).ContinueWith(c =>
    {
        for (int i = 0; i < 50; i++)
        {
            Console.WriteLine(i + "这是Task在运行");
        }
    });
    for (int i = 0; i < 100; i++)
    {
        Console.WriteLine(i + "这是Task之后的程序在运行");
    }

调试的时候你会发现,刚开始的时候的时候是先显示的"i这是Task之后的程序在运行"

之后在等带了30毫秒,后就会开始显示"i这是Task在运行"和"i这是Task之后的程序在运行"交叉显示

运行结果如下:


3.4.2 使用Task.Delay()和Awaiter实现延迟工作

示例:运行效果同上

    Task.Delay(30).GetAwaiter().OnCompleted(() =>
    {
        for (int i = 0; i < 50; i++)
        {
            Console.WriteLine(i + "这是Awaiter在运行行");
        }
    });
    for (int i = 0; i < 100; i++)
    {
        Console.WriteLine(i + "这是Awaiter之后的程序在运行行");
    }
    Console.ReadKey();    


3.5 Task对象的其他一些静态方法

方法名 说明
Task.Wait task1.Wait();就是等待任务执行(task1)完成,task1的状态变为Completed
Task.WaitAll 待所有的任务都执行完成
Task.WaitAny 发同Task.WaitAll,就是等待任何一个任务完成就继续向下执行
CancellationTokenSource 通过cancellation的tokens来取消一个Task


3.6 取消异步操作

异步方法是可以请求终止运行的,

System.Threading.Tasks命名空间中有两个类是为此目的而设计的:Cance1lationToken和CancellationTokenSource。

下面看使用CancellationTokenSource和CancellationToken来实现取消某个异步操作。

这里使用Task.Run()为例,其第一个参数是一个Action委托,第二个参数就是CancellationToken对象

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();//生成一个CancellationTokenSource对象,该对象可以创建CancellationToken                                                 
    CancellationToken ct = cts.Token;//获取一个令牌(token)
    Task.Run(() =>
    {
        for (int i = 0; i < 20; i++)
        {
            if (ct.IsCancellationRequested)
            {
                return;
            }
            Thread.Sleep(1000);
            Console.WriteLine($"异步程序的的循环:{i}");
        }
    }, ct);//注意Run()的第二个参数就是终止令牌token
    for (int i = 0; i < 4; i++)
    {

        Thread.Sleep(1000);
        Console.WriteLine($"主线程中循环:{i}");
    }
    Console.WriteLine("马上sts.Cancel(),即将要终止异步程序");
    cts.Cancel();//含有该CancellationTokenSource的token的异步程序,终止!
    Console.ReadKey();
}

运行结果:可以发现异步任务Task.Run()还没有完成,但是因为cst.Cancel()运行,token的属性IsCancellationRequested变为true,异步循环结束。

说明:取消一个异步操作的过程,注意,该过程是协同的。

即:调用CancellationTokenSource的Cancel时,它本身并不会执行取消操作。
而是会将CancellationToken的IsCancellationRequested属性设置为true。
包含CancellationToken的代码负责检查该属性,并判断是否需要停止执行并返回。




4.并行Linq(PLinq)


4.1 AsParallel()

System.Linq名称空间中有一个ParallelEnumerable类,该类中的方法可以分解Linq查询的工作,使其分布在多个线程上,即实现并行查询。

为并行运行而设计的LINQ查询称为PLINQ查询

下面让我们先简单的理一理:

首先我们都知道Enumerable类为IEnumberable<T>接口扩展了一系列的静态方法。(就是我们使用Linq方法语法的中用的哪些常用的静态方法,自行F12)

正如MSDN中所说:“ParallelEnumberable是Enumberable的并行等效项”,ParallelEnumberable类则是Enumerable类的并行版本,

F12查看定义可以看到ParallelEnumerable类中几乎所有的方法都是对ParallelQuery<TSource>接口的扩展,

但是,在ParallelEnumberable类有一个重要的例外,AsParallel() 方法还对IEnumerable<T>接口的扩展,并且返回的是一个ParallelQuery<TSource>类型的对象,

所以呢?凡是实现类IEnumberable<T>集合可以通过调用静态方法AsParallel(),返回一个ParallelQuery 类型的对象,之后就可以使用ParallelEnumerable类中的异步版本的静态查询方法了!

Note that when running PLinq, PLinq will automatically determine if the query parallelization benefit from, will run concurrently. If parallel query execution compromising performance, PLINQ queries run in sequence.

Example: to find a number divisible by 3 of the 50 million will ask the result is stored in reverse modThreeIsZero [] in

This is very much needed repeat operations, so we can compare the manner and PLinq general Linq query query, compared to some of the time needed.

static void Main(string[] args)
{
    int[] intArray = Enumerable.Range(1, 50000000).ToArray();
    Stopwatch sw = new Stopwatch();

    //顺序查询
    sw.Start();
    int[] modThreeIsZero1 = intArray.Select(n => n).Where(n => n % 3 == 0).OrderByDescending(n => n).ToArray();
    sw.Stop();
    Console.WriteLine($"顺序查询,运行时间:{sw.ElapsedMilliseconds}毫秒,可以整除3的个数:{modThreeIsZero1.Count()}");

    //使用AsParallel()实现并行查询
    //AsParallel()方法返回ParallelQuery&lt;TSourc>类型对象。因为返回的类型,所以编译器选择的Select()、Where()等方法是ParallelEnumerable.Where(),而不是Enumerable.Where()。
    sw.Restart();
    int[] modThreeIsZero2 = intArray.AsParallel().Select(n => n).Where(n => n % 3 == 0).OrderByDescending(n => n).ToArray();
    sw.Stop();
    Console.WriteLine($"并行查询,运行时间:{sw.ElapsedMilliseconds}毫秒,可以整除3的个数:{modThreeIsZero2.Count()}");

    Console.ReadKey();
}

Description: AsParallel () method returns ParallelQuery <TSourc> type of object. Because the return type, the compiler selection Select (), Where () or the like is ParallelEnumerable.Where (), instead Enumerable.Where ().

operation result:

The results can be compared in scale Linq query, the query and synchronous parallel query run time gap between the two is still very large!

But the effect of both small-scale Linq query is actually not very obvious.

4.2 Cancellation parallel query

3.6 cancel an asynchronous operation explains how to cancel a long task,

So for PLinq long-running also can be canceled

Also used CancellationTokenSourceto generate an CancellationTokenobject as a token

How the token to PLinq it? Use ParallelQuery<TSource>the static methodWithCancellation(token)

In PLinq, the parallel operations if canceled, will throwOperationCanceledException

Example:

static void Main(string[] args)
{
    //具体的作用和含义可以看0030取消一个异步操作
    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken ct = cts.Token;
    int[] intArray = Enumerable.Range(1, 50000000).ToArray();
    Task<int[]> task = Task.Run(() =>
    {
        try
        {
            int[] modThreeIsZero = intArray.AsParallel().WithCancellation(ct).Select(n => n).Where(n=> n% 3 == 0).OrderByDescending(n => n).ToArray();
            return modThreeIsZero;
        }
        catch (OperationCanceledException ex)//一旦PLinq中取消查询就会触发OperationCanceledException异常
        {
            Console.WriteLine(ex.Message);//注意:Message的内容就是:已取消该操作
            return null;
        }
    });
       
    Console.WriteLine("取消PLinq?Y/N");
    string input = Console.ReadLine();
    if (input.ToLower().Equals("y"))
    {
        cts.Cancel();//取消并行查询
        Console.WriteLine("取消了PLinq!");//undone:怎么验证已经真的取消了
    }
    else
    {
        Console.WriteLine("Loading……");
        Console.WriteLine(task.Result.Count());
    }
    Console.ReadKey();
}




5. Reference & Source Codes

Books: proficient in C #

Books: C # Advanced Programming

Books: The US website development of ASP.NET MVC5

Documentation: .NET API browser

Click: source code download

Alas, the book really imagined thick, are all kick the tires, first right here!

Guess you like

Origin www.cnblogs.com/shanzhiming/p/12315548.html