C# Task Parallel Library,TPL

2023-11-10 04:59

本文主要是介绍C# Task Parallel Library,TPL,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

System.Threading.Tasks

前言:

我们之前介绍了两种构建多线程软件的编程技术(使用异步委托或通过System.Threading的成员)。这两个可以在任何版本的.NET平台工作。

关于System.Threading 的介绍

关于 System.Threading.Tasks的介绍

  从.NET4.0开始,微软引入了一种全新的多线程应用程序开发方法,即使用TPL并行编程库。使用System.Threading.Tasks中的类型,可以构建可扩展的并行代码,而不必直接与线程和线程池打交道。我们使用TPL的时候也可以使用System,Thrading。这两种线程工具可以非常自然地一起工作。

  总体而言,System.Threading.Tasks中的类型被称为任务并行库(Task Parallel  Library,TPL)。

此命名空间中的一些类。

 

Parallel类的作用

TPL中一个十分重要的类时 System.Threading.Tasks.Parallel ,它提供了大量方法,能够以并行的方式迭代数据集合(实现了IEnumerable<T>的对象)。在SDK文档中查看Parallel类,你会发现该类支持两个主要的静态方法——Parallel.For()和Parallel.FoeEach(),每个方法都有很大的重载版本。

并行解释:我们知道foreach里面循环一次,然后再循环,再循环,那并行说的是尽可能把这些分开的一次次循环放在一起执行。加快了速度。

方法中的参数有两个可能需要了解一下,等具体在用到的时候在解释。

出了for,foreach方法外:

 

    这些方法可以用来编写并行执行的码体。这些语句的逻辑与普通循环(使用for或foreach关键字)中的逻辑完全相同。好处是,Parallel类将从线程池中为我们提取线程(和管理并发)。这个方法里面还需要使用System.Func<T>System.Action<T>委托(到时候会专门介绍委托的时候在仔细介绍)来指定要调用的处理数据方法。

Func<T>委托:表示一个拥有给定返回值和不同数量参数的方法。

Action<t>委托:表示指向有几个参数的方法,但返回 void.

我们在使用For(),ForEach()方法时可以传递强类型的Func<T>或Action<T>委托对象,你也可以使用恰当的C#匿名方法或Lambda表达式来简化编程。

 

使用Parallel类的数据并行

  使用TPL的第一种方式是执行数据并行。使用For,ForEach方法以并行方式对数组或集合中的数据进行迭代。

列子:我们把一个文件夹中的图片,进行翻转,然后保存在别的文件夹中

普通的写法:

 

 private void ProcessFiles() 
{string[] files = Directory.GetFiles(@"C:\Users\Seali\Pictures\小牛电动", "*.*",    SearchOption.AllDirectories);string newDir = @"C:\ModefiedPictures";Directory.CreateDirectory(newDir);foreach (string item in files){string filename = Path.GetFileName(item);using (Bitmap bitma = new Bitmap(item)){bitma.RotateFlip(RotateFlipType.Rotate180FlipNone);//反转180度bitma.Save(Path.Combine(newDir, filename)); //保存this.Invoke((Action)delegate{textBox1.Text = string.Format("Processing {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId);});}}
}
private void button1_Click(object sender, EventArgs e)
{System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); //用来计算运行时间ProcessFiles();double aa=sw.ElapsedMilliseconds / 1000.0;MessageBox.Show(aa+"s"); //计算我们完成这个方法用了多长时间
}

 

当我们执行此方法的时候,开始就一直卡这,结束了我们只能看到文本框上显示最后一次的名字,因为我们的线程阻塞了。因为我们在等待这个过程,所有卡顿。

换成我们的Parallel类的方法试一下

 

private void ProcessFiles()
{string[] files = Directory.GetFiles(@"C:\Users\Seali\Pictures\小牛电动", "*.*",    SearchOption.AllDirectories);string newDir = @"C:\ModefiedPictures";Directory.CreateDirectory(newDir);Parallel.ForEach(files, item =>       //对于集合处理很容易{string filename = Path.GetFileName(item);using (Bitmap bitma = new Bitmap(item)){bitma.RotateFlip(RotateFlipType.Rotate180FlipNone);bitma.Save(Path.Combine(newDir, filename));this.Invoke((Action)delegate     //在form对象上调用,允许次线程以线程安全的方式访问控件{    textBox1.Text = string.Format("Processing {0} on thread {1}", filename,Thread.CurrentThread.ManagedThreadId);    });}});
}

 

虽然速度上快了一些,但依然阻塞了。有没有办法让我们的线程不再阻塞,当然是有的,介绍我们的第二个类。

 

Task

 定义:表示一个异步操作。可以作为异步委托的简单替代品。

 

部分构造函数:

部分属性:

本文由机器翻译。若要查看英语原文,请勾选“英语”复选框。 也可将鼠标指针移到文本上,在弹出窗口中显示英语原文。

翻译

英语

TaskFactory 类

提供对创建和计划 Task 对象的支持。

部分方法:(此方法有很多的重载)

 

传入的委托,指向以异步方式进行调用的方法。居然是异步执行了,我们的主线程不再卡顿了,每循环一次文本框都会变一次了。

跟新我们的代码:

 

//创建一个新的任务来处理文件
Task.Factory.StartNew(() =>
{System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); //用来计算运行时间ProcessFiles();double aa = sw.ElapsedMilliseconds / 1000.0;MessageBox.Show(aa + "s");
});

 

我们也可以取消请求。

Parallel.For()和Pallel.ForEach()方法都支持取消标记,我们调用方法时,传入一个ParallelOption对象,它包含一个CancekkationTokenSource对象。

 

 

ParallelOptions 类

存储配置的方法的操作的选项 Parallel 类。

 属性:

CancellationToken 结构

传播有关应取消操作的通知。

 

 除此之外我们还需要了解

CancellationTokenSource 类

向应该被取消的 CancellationToken 发送信号。

 

 

 所有,我们新加一个取消按钮。完整代码如下 

 

private CancellationTokenSource cancelToken = new CancellationTokenSource();
public Form1()
{InitializeComponent();
}private void button1_Click(object sender, EventArgs e)
{//创建一个新的任务来处理文件Task.Factory.StartNew(() =>{System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); //用来计算运行时间ProcessFiles();double aa = sw.ElapsedMilliseconds / 1000.0;MessageBox.Show(aa + "s");});     
}private void ProcessFiles()
{//设置参数ParallelOptions parOpts = new ParallelOptions();parOpts.CancellationToken = cancelToken.Token;parOpts.MaxDegreeOfParallelism = System.Environment.ProcessorCount;string[] files = Directory.GetFiles(@"C:\Users\Seali\Pictures\小牛电动", "*.*",    SearchOption.AllDirectories);string newDir = @"C:\ModefiedPictures";Directory.CreateDirectory(newDir);try{Parallel.ForEach(files, item =>{parOpts.CancellationToken.ThrowIfCancellationRequested();//抛异常string filename = Path.GetFileName(item);using (Bitmap bitma = new Bitmap(item)){bitma.RotateFlip(RotateFlipType.Rotate180FlipNone);bitma.Save(Path.Combine(newDir, filename));this.Invoke((Action)delegate     //在form对象上调用,允许次线程以线程安全的方式访问控件{textBox1.Text = string.Format("Processing {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId);});}});}catch (OperationCanceledException ex){this.Invoke((Action)delegate //在form对象上调用,允许次线程以线程安全的方式访问控件{textBox1.Text = ex.Message;});}
}private void button2_Click(object sender, EventArgs e)
{//停止所有的工作者线程cancelToken.Cancel();
}

以上介绍的是数据并行处理。

使用注意:如果进行数据并行处理,循环的时候是字符串的追加,很小的机会会报错,这个需要注意下

例如:  我把循环换成了这个,发现有时候页面上显示有问题,上一次还没写完,下一次就已经开始了,然后拼接字符串就出现了问题,如果前一次跟下一次做的操作没什么联系用这个就很好

 

//Parallel.ForEach(dt.AsEnumerable().AsParallel(),
//item =>
//{
//    sb.Append("<section class=\"list\"   ><span class=\"wenbe hundred\">");
//    sb.AppendFormat("<p><u>编号:</u><em>{0}</em></p>", item["ContractNumber"]);
//    sb.AppendFormat("<p><u>合同名称:</u><em>{0}</em></p>", item["ContractName"]);
//    sb.AppendFormat("<p><u>签约单位/个人:</u><em>{0}</em></p>", item["ContractUnit"]);
//    sb.AppendFormat("<p><u>签约时间:</u><em>{0}</em></p>", Convert.ToDateTime(item["ContractTime"]).ToString("yyyy年MM月dd日 HH:mm:ss"));
//    sb.AppendFormat("<p><u>合同类型:</u><em>{0}</em></p>", item["ContractTypeName"]);
//    sb.AppendFormat("<p><u>责任社工:</u><em>{0}</em></p>", item["UserName"]);
//    sb.Append("</span>");
//    sb.Append("<section class=\"button\">");
//    if (audit)
//    {
//        sb.AppendFormat("<a href=\"javascript:showDiv('{0}');\">审核</a>", item["ID"]);
//    }
//    sb.Append("</section></section>");
//});

 

小提示:如何对DataTable里面的行并行处理。

按照我们的理解,我们把一个参数给个可迭代的类型,第二个参数给个委托,

这样写视乎没毛病,第一个参数是个集合,然而显示错误,如果你基础好的话你应该知道,可以迭代的集合需要实现IEnumerable接口或声明GetEnumerator方法的类型,

我们dt.Rows返回的仅仅只是个集合类型,并没实现这两个条件中的一个。

有一个非常简单的方法可以知道你的集合适不适用与迭代,把你的集合打点 看下有没有 AsEnumerable()  此方法,有的话就可以。

把集合换成这个就可以了

dt.AsEnumerable()

更多转换问题就可以参数LinQ语法

 

使用并行类的任务并行

  TPL还可以使用Parallel.Invoke()方法轻松触发多个异步任务.

列子:

点击下载按钮,从别的网站下载类容,然后在进行查询

string theBook;
private void btnDownload_Click(object sender, EventArgs e)
{//从别的网站下载数据,并把获取的数据赋值给文本框WebClient wc = new WebClient();  //这是System.Net里面的类//这个完成事件自己声明//wc.DownloadStringCompleted += wc_DownloadStringCompleted;//利用委托来完成事件  效果一样wc.DownloadStringCompleted += (s, eArg) =>{theBook = eArg.Result;  //得到下载的数据txtBook.Text = theBook;};           //下载数据  不会阻止线程wc.DownloadStringAsync(new Uri("http://www.gutenberg.org/files/98/98-8.txt"));   
}void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{theBook = e.Result;txtBook.Text = theBook;
}

 

然后我们找出最长的单词和出现次数最多的10个单词

private void btnGetStatus_Click(object sender, EventArgs e)
{//从电子书中获取单词string[] words = theBook.Split(new char[] { ' ', '\u000A', ',', '.', ';', '-', '?', '/' }, StringSplitOptions.RemoveEmptyEntries);//找到最常用的个单词string[] tenMostCommon = null;  //= FindTenMostCommon(words);//获取最长的单词string longesWord = null;// = FindLongesWord(words);tenMostCommon = FindTenMostCommon(words);longesWord = FindLongesWord(words);StringBuilder sb = new StringBuilder("最常见的单词有:\n");foreach (string item in tenMostCommon){sb.AppendLine(item);}sb.AppendFormat("最长的单词是:{0}", longesWord);sb.AppendLine();MessageBox.Show(sb.ToString(), "Book info");
}    //查找出现次数前十单词
private string[] FindTenMostCommon(string[] words)
{var freQuencyOrder = from word in words where word.Length > 6 group word by word into g orderby g.Count() descending select g.Key;string[] commonWords = (freQuencyOrder.Take(10)).ToArray();return commonWords;
}//查找最长的单词
private string FindLongesWord(string[] words)
{return (from word in words orderby word.Length descending select word).FirstOrDefault();
}

可以修改我买的方法,让应用程序使用所有计算机中可用的CUP,加快速度 

 

 

//尽可能的同时执行这两个方法
Parallel.Invoke(() =>{tenMostCommon = FindTenMostCommon(words);}, () =>{longesWord = FindLongesWord(words);}
);

 

并行LINQ查询(PLINQ)

在System.Linq中的 ParallelEnumerable类提供了一扩展方法

定义:提供一组用于查询实现 ParallelQuery{TSource} 的对象的方法。 此命令的并行等效 Enumerable

它的扩展方法太多了,这里只写几个

例子:

 

 private void btnExecute_Click(object sender, EventArgs e)
{Task.Factory.StartNew(() =>  //防止线程阻塞{ProcessInfoData();});
}private void ProcessInfoData()
{int[] soure = Enumerable.Range(1, 10000000).ToArray();//生成一个很大的数组int[] modThreeIsZero = null;modThreeIsZero = (from num in soure where num % 3 == 0 orderby num descending select num).ToArray<int>();MessageBox.Show(string.Format("found {0} numbers that query", modThreeIsZero.Count()));
}

使用PLINQ查询

改动一下代码,如果可以使用TPL并行的执行该查询,调用AsParallel()

 

modThreeIsZero = (from num in soure.AsParallel() where num % 3 == 0 orderby num descending select num).ToArray<int>();

取消PLINQ查询,跟上面的类似,把状态传过来就可以了,看一下完整版

private CancellationTokenSource cancelToken = new CancellationTokenSource();
private void btnExecute_Click(object sender, EventArgs e)
{Task.Factory.StartNew(() =>  //防止线程阻塞{ProcessInfoData();});
}private void ProcessInfoData()
{int[] soure = Enumerable.Range(1, 10000000).ToArray();//生成一个很大的数组int[] modThreeIsZero = null;try{modThreeIsZero = (from num in soure.AsParallel() where num % 3 == 0 orderby num descending select num).ToArray<int>();MessageBox.Show(string.Format("found {0} numbers that query", modThreeIsZero.Count()));}catch (OperationCanceledException ex){this.Invoke((Action)delegate{this.Text = ex.Message;});}
}private void btnCancel_Click(object sender, EventArgs e)
{cancelToken.Cancel();
}

 

.NET 4.5 下的异步调用

注意啦,使用这个,你的版本需要到达4.5哦。

此版本新增了两个关键字,来简化了编写异步代码的过程。async和await关键字。

 

C#async和await关键字初深

   C#async关键字用来指定某个方法、Lambda表达式或匿名方法自动以一部的方式来调用。在调用async方法时,await关键字自动暂停但前线程中任何其他活动,直到任务完成,离开调用线程。

 例如:

 

private   void btnCallMethod_Click(object sender, EventArgs e)
{txtInput.Text =  DoWorkAsync();
}private  string DoWorkAsync() 
{Thread.Sleep(10000);return "Down with work!"; 
}

 

当我点击按钮的时候,需要等待10秒钟,文本框才能接受到类容,线程也阻塞了。用上面的方法实现起来需要写很多。但在.NET4.5下,我们可以这么写

在写之前,你要了解:

 

T代表返回的类型。

 

//async关键字修饰此方法
private  async  void btnCallMethod_Click(object sender, EventArgs e)   
{txtInput.Text = await DoWorkAsync();   //使用await接受类容   记住一点就可以了  async修饰了方法,里面一定要用await修饰Task.Run()
}private  Task<string>  DoWorkAsync() 
{//异步执行var d= Task.Run(() =>{Thread.Sleep(10000);return "Down with work!";});/先弹框MessageBox.Show(d.GetType().ToString());return d; 
}

 

 

此方法作为非阻塞调用。在被调用的方法名前面使用了await关键字。这很重要:如果async关键字修饰某个方法,但内部没有一个方法await方法调用,任会构建一个阻塞。

DoWork()的实现直接返回Task<T>对象,它是Task.Run()的返回值。   Task.Run(  retun T:)   Task,T>  

 

异步方法的命名预定 

 任何返回Task的方法都用“Async”作为后缀。

 

返回void的异步方法

 

private async Task MethodAsync() 
{await Task.Run(() =>{Thread.Sleep(4000);});
}

 

单个async方法中可以拥有多个await上下文

 

private async void button2_Click(object sender, EventArgs e)
{await Task.Run(() => { Thread.Sleep(2000); });MessageBox.Show("我在做第一件事");await Task.Run(() =>{Thread.Sleep(2000);});MessageBox.Show("我在做第二件事");await Task.Run(()=>{Thread.Sleep(200);});MessageBox.Show("我在做第三件事");
}

 

执行了此方法,每等待两秒才会弹一次框,不会像上面那样先弹框。

关键点:

方法(包括Lambda表达式和匿名方法)可以用async关键字标记,允许该方法以非阻塞的形式进行工作

②用async关键字标记的方法(包括Lambda表达式和匿名方法)在遇到await关键字之前将以阻塞的形式运行

③单个async方法可以拥有多个await上下文

当遇到await表达式时,调用线程将挂起,直到await的任务完成。同时,控制将返回给方法的调用者(解释了为什么每等待2秒才弹框)

⑤await关键字将从视图中隐藏返回Task对象,直接返回实际的返回值。没有返回值的方法返回可以简单的返回void.

⑥根据命名预定,要被异步调用的方法应该以“Async”作为后缀

 

改进我们在System.Threading中的代码

//此方法不能用async标记
static void Main(string[] args)
{AddAsync();Console.ReadLine();
}private static async Task AddAsync()
{Console.WriteLine("开始你的表演:");Console.WriteLine("ID of thread in Mian():{0}",    Thread.CurrentThread.ManagedThreadId);await Sum(10, 20);Console.WriteLine("其他的线程做完了");
}static async Task Sum(int a, int b)
{await Task.Run(() =>{Console.WriteLine("ID of thread in Add():{0}", Thread.CurrentThread.ManagedThreadId);Console.WriteLine("{0}+{1} ={2}", a, b, a + b);});
}

 

速度是相当的快。 

基础也就介绍到这了。

 

这篇关于C# Task Parallel Library,TPL的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/380499

相关文章

2. c#从不同cs的文件调用函数

1.文件目录如下: 2. Program.cs文件的主函数如下 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Windows.Forms;namespace datasAnalysis{internal static

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

C# dateTimePicker 显示年月日,时分秒

dateTimePicker默认只显示日期,如果需要显示年月日,时分秒,只需要以下两步: 1.dateTimePicker1.Format = DateTimePickerFormat.Time 2.dateTimePicker1.CustomFormat = yyyy-MM-dd HH:mm:ss Tips:  a. dateTimePicker1.ShowUpDown = t

C#关闭指定时间段的Excel进程的方法

private DateTime beforeTime;            //Excel启动之前时间          private DateTime afterTime;               //Excel启动之后时间          //举例          beforeTime = DateTime.Now;          Excel.Applicat

C# 防止按钮botton重复“点击”的方法

在使用C#的按钮控件的时候,经常我们想如果出现了多次点击的时候只让其在执行的时候只响应一次。这个时候很多人可能会想到使用Enable=false, 但是实际情况是还是会被多次触发,因为C#采用的是消息队列机制,这个时候我们只需要在Enable = true 之前加一句 Application.DoEvents();就能达到防止重复点击的问题。 private void btnGenerateSh

C# double[] 和Matlab数组MWArray[]转换

C# double[] 转换成MWArray[], 直接赋值就行             MWNumericArray[] ma = new MWNumericArray[4];             double[] dT = new double[] { 0 };             double[] dT1 = new double[] { 0,2 };

C# Hash算法之MD5、SHA

MD5我们用的还是比较多的,一般用来加密存储密码。但是现在很多人觉MD5可能不太安全了,所以都用上了SHA256等来做加密(虽然我觉得都差不多,MD5还是能玩)。 还是跟上一篇说的一样,当一个算法的复杂度提高的同时肯定会带来效率的降低,所以SHA和MD5比较起来的话,SHA更安全,MD5更高效。 由于HASH算法的不可逆性,所以我认为MD5和SHA主要还是应用在字符串的"加密"上。 由于

C#线程系列(1):BeginInvoke和EndInvoke方法

一、线程概述 在操作系统中一个进程至少要包含一个线程,然后,在某些时候需要在同一个进程中同时执行多项任务,或是为了提供程序的性能,将要执行的任务分解成多个子任务执行。这就需要在同一个进程中开启多个线程。我们使用 C# 编写一个应用程序(控制台或桌面程序都可以),然后运行这个程序,并打开 windows 任务管理器,这时我们就会看到这个应用程序中所含有的线程数,如下图所示。

C#设计模式(1)——单例模式(讲解非常清楚)

一、引言 最近在学设计模式的一些内容,主要的参考书籍是《Head First 设计模式》,同时在学习过程中也查看了很多博客园中关于设计模式的一些文章的,在这里记录下我的一些学习笔记,一是为了帮助我更深入地理解设计模式,二同时可以给一些初学设计模式的朋友一些参考。首先我介绍的是设计模式中比较简单的一个模式——单例模式(因为这里只牵涉到一个类) 二、单例模式的介绍 说到单例模式,大家第一