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

 任务和线程的区别:

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

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

 一、认识Task和Task的基本使用

1、认识Task

首先来看一下Task的继承结构。Task标识一个异步操作。

可以看到Task和Thread一样,位于System.Threading命名空间下,这也就是说他们直接有密不可分的联系。下面我们来仔细看一下吧!

2、创建Task

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

        //第一种创建方式,直接实例化
var task1 = new Task(() =>
{
//TODO you code
});

这是最简单的创建方法,可以看到其构造函数是一个Action,其构造函数有如下几种,比较常用的是前两种。

        //第二种创建方式,工厂创建
var task2 = Task.Factory.StartNew(() =>
{
//TODO you code
});

这种方式通过静态工厂,创建以个Task并运行。下面我们来建一个控制台项目,演示一下,代码如下:

要添加System.Threading.Tasks命名控件引用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace TaskDemo
{
class Program
{
static void Main(string[] args)
{
var task1 = new Task(() =>
{
Console.WriteLine("Hello,task");
});
task1.Start(); var task2 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Hello,task started by task factory");
}); Console.Read();
}
}
}

这里我分别用两种方式创建两个task,并让他们运行。可以看到通过构造函数创建的task,必须手动Start,而通过工厂创建的Task直接就启动了。

下面我们来看一下Task的声明周期,编写如下代码:

      var task1 = new Task(() =>
{
Console.WriteLine("Begin");
System.Threading.Thread.Sleep(2000);
Console.WriteLine("Finish");
});
Console.WriteLine("Before start:" + task1.Status);
task1.Start();
Console.WriteLine("After start:" + task1.Status);
task1.Wait();
Console.WriteLine("After Finish:" + task1.Status); Console.Read();

  task1.Status就是输出task的当前状态,其输出结果如下:

可以看到调用Start前的状态是Created,然后等待分配线程去执行,到最后执行完成。

从我们可以得出Task的简略生命周期:

Created:表示默认初始化任务,但是“工厂创建的”实例直接跳过。

WaitingToRun: 这种状态表示等待任务调度器分配线程给任务执行。

RanToCompletion:任务执行完毕。

 二、Task的任务控制

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

1、Task.Wait

在上个例子中,我们已经使用过了,task1.Wait();就是等待任务执行完成,我们可以看到最后task1的状态变为Completed。

2、Task.WaitAll

看字面意思就知道,就是等待所有的任务都执行完成,下面我们来写一段代码演示一下:

    static void Main(string[] args)
{
var task1 = new Task(() =>
{
Console.WriteLine("Task 1 Begin");
System.Threading.Thread.Sleep(2000);
Console.WriteLine("Task 1 Finish");
});
var task2 = new Task(() =>
{
Console.WriteLine("Task 2 Begin");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Task 2 Finish");
}); task1.Start();
task2.Start();
Task.WaitAll(task1, task2);
Console.WriteLine("All task finished!"); Console.Read();
}

其输出结果如下:

可以看到,任务一和任务二都完成以后,才输出All task finished!

3、Task.WaitAny

这个用发同Task.WaitAll,就是等待任何一个任务完成就继续向下执行,将上面的代码WaitAll替换为WaitAny,输出结果如下:

4、Task.ContinueWith

就是在第一个Task完成后自动启动下一个Task,实现Task的延续,下面我们来看下他的用法,编写如下代码:

static void Main(string[] args)
{
var task1 = new Task(() =>
{
Console.WriteLine("Task 1 Begin");
System.Threading.Thread.Sleep(2000);
Console.WriteLine("Task 1 Finish");
});
var task2 = new Task(() =>
{
Console.WriteLine("Task 2 Begin");
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Task 2 Finish");
}); task1.Start();
task2.Start();
var result = task1.ContinueWith<string>(task =>
{
Console.WriteLine("task1 finished!");
return "This is task result!";
}); Console.WriteLine(result.Result.ToString()); Console.Read();
}

执行结果如下:

可以看到,task1完成之后,开始执行后面的内容,并且这里我们取得task的返回值。

在每次调用ContinueWith方法时,每次会把上次Task的引用传入进来,以便检测上次Task的状态,比如我们可以使用上次Task的Result属性来获取返回值。我们还可以这么写:

var SendFeedBackTask = Task.Factory.StartNew(() => { Console.WriteLine("Get some Data!"); })
.ContinueWith<bool>(s => { return true; })
.ContinueWith<string>(r =>
{
if (r.Result)
{
return "Finished";
}
else
{
return "Error";
}
});
Console.WriteLine(SendFeedBackTask.Result);

首先输出Get some data,然后执行第二个获得返回值true,最后根据判断返回Finished或error。输出结果:

Get some Data!

Finished

其实上面的写法简化一下,可以这样写:

Task.Factory.StartNew<string>(() => {return "One";}).ContinueWith(ss => { Console.WriteLine(ss.Result);});

输出One,这个可以看明白了吧~

更多ContinueWith用法参见:http://technet.microsoft.com/zh-CN/library/dd321405

5、Task的取消

前面说了那么多Task的用法,下面来说下Task的取消,比如我们启动了一个task,出现异常或者用户点击取消等等,我们可以取消这个任务。

如何取消一个Task呢,我们通过cancellation的tokens来取消一个Task。在很多Task的Body里面包含循环,我们可以在轮询的时候判断IsCancellationRequested属性是否为True,如果是True的话就return或者抛出异常,抛出异常后面再说,因为还没有说异常处理的东西。

下面在代码中看下如何实现任务的取消,代码如下:

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Factory.StartNew(() =>
{
for (var i = 0; i < 1000; i++)
{
System.Threading.Thread.Sleep(1000);
if (token.IsCancellationRequested)
{
Console.WriteLine("Abort mission success!");
return;
}
}
}, token);
token.Register(() =>
{
Console.WriteLine("Canceled");
});
Console.WriteLine("Press enter to cancel task...");
Console.ReadKey();
tokenSource.Cancel();
         Console.ReadKey();//这句忘了加,程序退出了,看不到“Abort mission success!“这个提示

这里开启了一个Task,并给token注册了一个方法,输出一条信息,然后执行ReadKey开始等待用户输入,用户点击回车后,执行tokenSource.Cancel方法,取消任务。其输出结果如下:

好了,今天先说道这里,明天继续讲task,接下来该说说task的异常处理和其他的一些用法,如果喜欢可以关注我,一有更新会马上通知你。

异步编程能充分利用多核计算机,提升程序性能。

请看如下程序:

static object o = new object();
static void Main(string[] args)
{ var tf = new TaskFactory();//线程池对象
Task t1 = tf.StartNew(TaskMenthod, "");//创建并启用线程方法1
Task t2 = Task.Factory.StartNew(TaskMenthod, "");//创建并启用线程方法2
Task t3 = new System.Threading.Tasks.Task(TaskMenthod, "");
t3.Start();//创建并启用线程方法3
Task t4 = Task.Run(() => TaskMenthod(""));//创建并启用线程方法4 //
Task t5 = Task.Run(() => TaskMenthod(""));
Task t6 = Task.Run(() => TaskMenthod(""));
Task t7 = Task.Run(() => TaskMenthod(""));
Task t8 = Task.Run(() => TaskMenthod(""));
Task t9 = Task.Run(() => TaskMenthod(""));
Task t10 = Task.Run(() => TaskMenthod(""));
Console.ReadKey();
} static void TaskMenthod(object title)
{
lock (o)//确保一个线程执行完方法后 其他线程才能进入
{
//连续输出四次
Console.WriteLine(title.ToString());
Console.WriteLine(title.ToString());
Console.WriteLine(title.ToString());
Console.WriteLine(title.ToString());
}
}

运行结果

因为Task是异步的,所以输出的结果不是按照1~10的顺序输出的,相信这点大家都能理解。

但是:

我们发现本应在一个方法体内连续输出的四个9却被分成了两部分,为何会这样呢?

这是因为多个Task同时访问TaskMenthod方法,他们之间是无序的,各个Task之间是没有优先级别的,因此会造成上述问题。

如何解决呢?

通过C# LOCK 可以解决,我们对TaskMenthod方法进行加锁即可,代码修正如下:

2、Task的Wait方法

运行代码如下:

  class Program
{
static object o = new object();
static void Main(string[] args)
{
var tf = new TaskFactory();//
Task t1 = tf.StartNew(TaskMenthod, "");
t1.Wait();//等待线程1 执行完毕后 继续执行
Task t2 = Task.Factory.StartNew(TaskMenthod, "");
t2.Wait();
Task t3 = new System.Threading.Tasks.Task(() => TaskMenthod(""));
t3.Start();
t3.Wait();
Task t4 = Task.Run(() => TaskMenthod(""));
t4.Wait(); //
Task t5 = Task.Run(() => TaskMenthod(""));
Task t6 = Task.Run(() => TaskMenthod(""));
Task t7 = Task.Run(() => TaskMenthod(""));
Task t8 = Task.Run(() => TaskMenthod(""));
Task t9 = Task.Run(() => TaskMenthod(""));
Task t10 = Task.Run(() => TaskMenthod(""));
Console.ReadKey();
} static void TaskMenthod(object title)
{
lock (o)//确保一个线程执行完方法后 其他线程才能进入
{
Console.WriteLine(title);
}
}
}

以上程序中t1 t2 t3 t4 这四个Task进行了Wait操作,也就是说:其他程序必须等待线程 1 2 3 4 执行完毕后,才能执行,而且线程 1 2 3 4 完全按照代码编写顺序进行输出。

三次执行结果如下:

由上图可知,线程1 2 3 4 的输出牢牢的把控了前四的位置,且是严格按照顺序输出的。

3、Task的WaitAll方法

将上述代码修改为如下:

class Program
{
static object o = new object();
static void Main(string[] args)
{
var tf = new TaskFactory();//线程池对象
Task t1 = tf.StartNew(TaskMenthod, "");
Task t2 = Task.Factory.StartNew(TaskMenthod, "");
Task t3 = new System.Threading.Tasks.Task(() => TaskMenthod(""));
t3.Start();
Task t4 = Task.Run(() => TaskMenthod(""));
Task.WaitAll(t1, t2, t3, t4); //优先执行线程1 2 3 4 线程 1 2 3 4 之间是异步的,也就是说执行顺序是无序的 待线程 1 2 3 4 执行完毕后 才会执行后续的线程 //
Task t5 = Task.Run(() => TaskMenthod(""));
Task t6 = Task.Run(() => TaskMenthod(""));
Task t7 = Task.Run(() => TaskMenthod(""));
Task t8 = Task.Run(() => TaskMenthod(""));
Task t9 = Task.Run(() => TaskMenthod(""));
Task t10 = Task.Run(() => TaskMenthod(""));
Console.ReadKey();
} static void TaskMenthod(object title)
{
lock (o)//确保一个线程执行完方法后 其他线程才能进入
{
Console.WriteLine(title);
}
}
}

上面有句注释:Task.WaitAll(t1, t2, t3, t4); //优先执行线程1 2 3 4 线程 1 2 3 4 之间是异步的,也就是说执行顺序是无序的 待线程 1 2 3 4 执行完毕后 才会执行后续的线程

三次执行结果:

由图可知,t1 t2 t3 t4 这四个线程优先执行,但是这四个线程之间的执行是异步的。理解这一点很重要。

继续探讨WaitAll方法

由代码 Task.WaitAll(t1, t2, t3, t4); 可知,必须等待 t1 t2 t3 t4 这四个线程执行完毕后,才会执行下面的操作。、

在此假设名字为t3的线程,我们将 t3.Start(); 去掉,看看会是社么结果:

代码如下:

    class Program
{
static object o = new object();
static void Main(string[] args)
{
var tf = new TaskFactory();//线程池对象
Task t1 = tf.StartNew(TaskMenthod, "");
Task t2 = Task.Factory.StartNew(TaskMenthod, "");
Task t3 = new System.Threading.Tasks.Task(() => TaskMenthod(""));
//t3.Start(); //t3 不执行了
Task t4 = Task.Run(() => TaskMenthod(""));
Task.WaitAll(t1, t2, t3, t4); //优先执行线程1 2 3 4 线程 1 2 3 4 之间是异步的,也就是说执行顺序是无序的 待线程 1 2 3 4 执行完毕后 才会执行后续的线程 //
Task t5 = Task.Run(() => TaskMenthod(""));
Task t6 = Task.Run(() => TaskMenthod(""));
Task t7 = Task.Run(() => TaskMenthod(""));
Task t8 = Task.Run(() => TaskMenthod(""));
Task t9 = Task.Run(() => TaskMenthod(""));
Task t10 = Task.Run(() => TaskMenthod(""));
Console.ReadKey();
} static void TaskMenthod(object title)
{
lock (o)//确保一个线程执行完方法后 其他线程才能进入
{
Console.WriteLine(title);
}
}
}

执行结果:

由此可知:因为 t3 的罢工,导致其他线程一直处于等待状态(得不到执行),如何解决这个问题呢?

还好,WaitAll方法提供的重载方法中有一个和时间结合的,如下:

将代码修改如下:

    class Program
{
static object o = new object();
static void Main(string[] args)
{
var tf = new TaskFactory();//线程池对象
Task t1 = tf.StartNew(TaskMenthod, "");
Task t2 = Task.Factory.StartNew(TaskMenthod, "");
Task t3 = new System.Threading.Tasks.Task(() => TaskMenthod(""));
//t3.Start(); //t3 不执行了 罢工了 咋的吧
Task t4 = Task.Run(() => TaskMenthod("")); Task[] tskArray = new Task[] { t1, t2, t3, t4 };
Task.WaitAll(tskArray,); //等待三秒后,执行后续线程 //
Task t5 = Task.Run(() => TaskMenthod(""));
Task t6 = Task.Run(() => TaskMenthod(""));
Task t7 = Task.Run(() => TaskMenthod(""));
Task t8 = Task.Run(() => TaskMenthod(""));
Task t9 = Task.Run(() => TaskMenthod(""));
Task t10 = Task.Run(() => TaskMenthod(""));
Console.ReadKey();
} static void TaskMenthod(object title)
{
lock (o)//确保一个线程执行完方法后 其他线程才能进入
{
Console.WriteLine(title);
}
}
}

这样,在程序等待三秒后,会执行后续操作!

4、WaitAny 方法,既是等待任意线程执行完毕后,执行后续方法

将上述程序中的WaitAll 修改成WaitAny 试一下

    class Program
{
static object o = new object();
static void Main(string[] args)
{
var tf = new TaskFactory();//线程池对象
Task t1 = tf.StartNew(TaskMenthod, "");
Task t2 = Task.Factory.StartNew(TaskMenthod, "");
Task t3 = new System.Threading.Tasks.Task(() => TaskMenthod(""));
//t3.Start(); //t3 不执行了 罢工了 咋的吧
Task t4 = Task.Run(() => TaskMenthod("")); Task.WaitAny(t1, t2, t3, t4); // //
Task t5 = Task.Run(() => TaskMenthod(""));
Task t6 = Task.Run(() => TaskMenthod(""));
Task t7 = Task.Run(() => TaskMenthod(""));
Task t8 = Task.Run(() => TaskMenthod(""));
Task t9 = Task.Run(() => TaskMenthod(""));
Task t10 = Task.Run(() => TaskMenthod(""));
Console.ReadKey();
} static void TaskMenthod(object title)
{
lock (o)//确保一个线程执行完方法后 其他线程才能进入
{
Console.WriteLine(title);
}
}
}

本节简单的介绍了Task的用法,希望大家喜欢!

@陈卧龙的博客

Task 异步编程测试案例及基础应用说明的更多相关文章

  1. Task异步编程

    Task异步编程中,可以实现在等待耗时任务的同时,执行不依赖于该耗时任务结果的其他同步任务,提高效率. 1.Task异步编程方法签名及返回值: a) 签名有async 修饰符 b) 方法名以 Asyn ...

  2. c#中的Task异步编程

    https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index翻译 1. 引入 Task异步 ...

  3. 新手浅谈C#Task异步编程

    Task是微软在.net framework 4.0发布的新的异步编程的利器,当然4.5新增了async.await,这儿我们先说Task相关. 在实际编程中,我们用的较多的是Task.Task.Fa ...

  4. 新手浅谈Task异步编程和Thread多线程编程

    初学Task的时候上网搜索,看到很多文章的标题都是task取代thread等等相关,我也一直以为task和thread是一类,其实task是.net4.0提出的异步编程,在之前.net1.0有dele ...

  5. vertx异步编程测试

    vertx是异步编程的框架,性能较高,开发简单.异步编程就是当一个请求来了,vertx将其交由一个事件进行处理,然后继续向下执行,等处理完成,返回结果,通知客户端.这是一个由服务端反向调用客户端的过程 ...

  6. Task异步编程,刨根到底

    1. 编译器到底对await做了什么 await 一个异步操作的时候,实际上编译器会创建一个状态机,这个状态机包含了调用者的上下文变量,状态机使用yield迭代器实现,状态机由clr调度,每次运行都会 ...

  7. .NET 4.5 Task异步编程学习资料

    参考资料: 1. http://www.cnblogs.com/heyuquan/archive/2013/04/18/3028044.html

  8. 『审慎』.Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历

    异步Task简单介绍 本标题有点 哗众取宠,各位都别介意(不排除个人技术能力问题) —— 接下来:我将会用一个小Demo 把 本文思想阐述清楚. .Net 4.0 就有了 Task 函数 —— 异步编 ...

  9. Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历

    Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历 https://www.cnblogs.com/shuxiaolong/p/DotNet_Task_BUG.html 异步Task简单 ...

随机推荐

  1. list中放map的几种方式

    package Test; import java.util.*; public class Test { public static void main(String[] args) { //第一种 ...

  2. 关注的Elasticsearch大牛博客

    1.http://wangnan.tech/ 2.https://elasticsearch.cn/people/wood 3.https://www.jianshu.com/u/244399b1d7 ...

  3. 操作过程-CentOS7下添加新硬盘扩充已经存在的逻辑卷分区的存储空间

    Linux添加硬盘扩充已有分区存储空间方式 总体步骤 磁盘初始化分区 创建物理卷 扩展卷组 扩展逻辑卷 通知文件系统生效 ​ 磁盘初始化分区 [root@oracledb ~]# fdisk -l 磁 ...

  4. SQLserver 还原数据库报“指定转换无效”的错的解决方案

    最近在附加一个数据库的时候遇到一个问题:“指定转换无效”,经过分析,出现这个问题的原因可能是:1.版本不对.2.文件加密.3.文件损坏 解决方法: 一.版本不对: 1.   首先确定源数据库版本,下面 ...

  5. php二维数组去重

    php二维数组去重 前言:php一维数组去重很简单,直接array_unique($arr)即可,但是二维数组去重就得自己去写了 二维数组去重方法: /* * 二维数组去重 * 注意:二维数组中的元素 ...

  6. eclipse中的tomcat配置

    打开Eclipse,单击“window”菜单,选择下方的“Preferences”:   找到Server下方的Runtime Environment,单击右方的Add按钮:   选择已经成功安装的T ...

  7. windows下设置JupyterNotebook默认目录

    目录 windows下设置JupyterNotebook默认目录 生成配置文件 设置默认工作目录 设置快捷方式中的目标与起始位置 直接修改anaconda中的相关配置文件 windows下设置Jupy ...

  8. js入门-文本框输入特定内容控制另一个文本框

    在填写表单时,有时需要某些文本框隐藏,当一文本框输入特定内容时才会显示隐藏的文本框,这一功能可以用onchange事件或oninput事件实现.下面对比下两种方法实现的区别: onchange()定义 ...

  9. January 06th, 2018 Week 01st Saturday

    In life the most interesting things tend to happen when you are on your way to do something else. 生活 ...

  10. leetcode 5. Longest Palindromic Substring [java]

    public String longestPalindrome(String s) { String rs = ""; int res = 0; for(int i = 0; i& ...