说起异步,Thread,Task,async/await,IAsyncResult 这些东西肯定是绕不开的,今天就来依次聊聊他们

1.线程(Thread)

多线程的意义在于一个应用程序中,有多个执行部分可以同时执行;对于比较耗时的操作(例如io,数据库操作),或者等待响应(如WCF通信)的操作,可以单独开启后台线程来执行,这样主线程就不会阻塞,可以继续往下执行;等到后台线程执行完毕,再通知主线程,然后做出对应操作!

在C#中开启新线程比较简单

static void Main(string[] args)

{

Console.WriteLine("主线程开始");

//IsBackground=true,将其设置为后台线程

Thread t = new Thread(Run) { IsBackground = true };

t.Start();

   Console.WriteLine("主线程在做其他的事!");

//主线程结束,后台线程会自动结束,不管有没有执行完成

//Thread.Sleep(300);

Thread.Sleep(1500);

Console.WriteLine("主线程结束");

}

static void Run()

{

Thread.Sleep(700);

Console.WriteLine("这是后台线程调用");

}

执行结果如下图

可以看到在启动后台线程之后,主线程继续往下执行了,并没有等到后台线程执行完之后。

1.1 线程池

试想一下,如果有大量的任务需要处理,例如网站后台对于HTTP请求的处理,那是不是要对每一个请求创建一个后台线程呢?显然不合适,这会占用大量内存,而且频繁地创建的过程也会严重影响速度,那怎么办呢?

线程池就是为了解决这一问题,把创建的线程存起来,形成一个线程池(里面有多个线程),当要处理任务时,若线程池中有空闲线程(前一个任务执行完成后,线程不会被回收,会被设置为空闲状态),则直接调用线程池中的线程执行(例asp.net处理机制中的Application对象),使用事例:

for (int i = 0; i < 10; i++)

{

ThreadPool.QueueUserWorkItem(m =>

{

Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());

});

}

Console.Read();

运行结果:

可以看到,虽然执行了10次,但并没有创建10个线程。

1.2 信号量(Semaphore)

Semaphore负责协调线程,可以限制对某一资源访问的线程数量,这里对SemaphoreSlim类的用法做一个简单的事例:

static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多只能有三个线程同时访问

static void Main(string[] args)

{

for (int i = 0; i < 10; i++)

{

new Thread(SemaphoreTest).Start();

}

Console.Read();

}

static void SemaphoreTest()

{

semLim.Wait();

Console.WriteLine("线程" + Thread.CurrentThread.ManagedThreadId.ToString() + "开始执行");

Thread.Sleep(2000);

Console.WriteLine("线程" + Thread.CurrentThread.ManagedThreadId.ToString() + "执行完毕");

semLim.Release();

}

执行结果如下:

可以看到,刚开始只有三个线程在执行,当一个线程执行完毕并释放之后,才会有新的线程来执行方法!

除了SemaphoreSlim类,还可以使用Semaphore类,感觉更加灵活,感兴趣的话可以搜一下,这里就不做演示了!

2.Task

Task是.NET4.0加入的,跟线程池ThreadPool的功能类似,用Task开启新任务时,会从线程池中调用线程,而Thread每次实例化都会创建一个新的线程。

Console.WriteLine("主线程启动");

//Task.Run启动一个线程

//Task启动的是后台线程,要在主线程中等待后台线程执行完毕,可以调用Wait方法

//Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task启动"); });

Task task = Task.Run(() => {

Thread.Sleep(1500);

Console.WriteLine("task启动");

});

Thread.Sleep(300);

task.Wait();

Console.WriteLine("主线程结束");

执行结果如下:

开启新任务的方法:Task.Run()或者Task.Factory.StartNew(),开启的是后台线程要在主线程中等待后台线程执行完毕,可以使用Wait方法(会以同步的方式来执行)。不用Wait则会以异步的方式来执行。

比较一下Task和Thread:

static void Main(string[] args)

{

for (int i = 0; i < 5; i++)

{

new Thread(Run1).Start();

}

for (int i = 0; i < 5; i++)

{

Task.Run(() => { Run2(); });

}

}

static void Run1()

{

Console.WriteLine("Thread Id =" + Thread.CurrentThread.ManagedThreadId);

}

static void Run2()

{

Console.WriteLine("Task调用的Thread Id =" + Thread.CurrentThread.ManagedThreadId);

}

执行结果:

可以看出来,直接用Thread会开启5个线程,用Task(用了线程池)开启了3个!

2.1 Task<TResult>

Task<TResult>就是有返回值的Task,TResult就是返回值类型。

Console.WriteLine("主线程开始");

//返回值类型为string

Task<string> task = Task<string>.Run(() => {

Thread.Sleep(2000);

return Thread.CurrentThread.ManagedThreadId.ToString();

});

//会等到task执行完毕才会输出;

Console.WriteLine(task.Result);

Console.WriteLine("主线程结束");

运行结果:

通过task.Result可以取到返回值,若取值的时候,后台线程还没执行完,则会等待其执行完毕!

简单提一下:

Task任务可以通过CancellationTokenSource类来取消,感觉用得不多,用法比较简单,感兴趣的话可以搜一下!

3. async/await

async/await是C#5.0中推出的,先上用法:

static void Main(string[] args)

{

Console.WriteLine("-------主线程启动-------");

Task<int> task = GetStrLengthAsync();

Console.WriteLine("主线程继续执行");

Console.WriteLine("Task返回的值" + task.Result);

Console.WriteLine("-------主线程结束-------");

}

static async Task<int> GetStrLengthAsync()

{

Console.WriteLine("GetStrLengthAsync方法开始执行");

//此处返回的<string>中的字符串类型,而不是Task<string>

string str = await GetString();

Console.WriteLine("GetStrLengthAsync方法执行结束");

return str.Length;

}

static Task<string> GetString()

{

   //Console.WriteLine("GetString方法开始执行")

return Task<string>.Run(() =>

{

Thread.Sleep(2000);

return "GetString的返回值";

});

}

async用来修饰方法,表明这个方法是异步的,声明的方法的返回类型必须为:void,Task或Task<TResult>。

await必须用来修饰Task或Task<TResult>,而且只能出现在已经用async关键字修饰的异步方法中。通常情况下,async/await成对出现才有意义,看看运行结果:

可以看出来,main函数调用GetStrLengthAsync方法后,在await之前,都是同步执行的,直到遇到await关键字,main函数才返回继续执行。

那么是否是在遇到await关键字的时候程序自动开启了一个后台线程去执行GetString方法呢?

现在把GetString方法中的那行注释加上,运行的结果是:

大家可以看到,在遇到await关键字后,没有继续执行GetStrLengthAsync方法后面的操作,也没有马上反回到main函数中,而是执行了GetString的第一行,以此可以判断await这里并没有开启新的线程去执行GetString方法,而是以同步的方式让GetString方法执行,等到执行到GetString方法中的Task<string>.Run()的时候才由Task开启了后台线程!

那么await的作用是什么呢?

可以从字面上理解,上面提到task.wait可以让主线程等待后台线程执行完毕,await和wait类似,同样是等待,等待Task<string>.Run()开始的后台线程执行完毕,不同的是await不会阻塞主线程,只会让GetStrLengthAsync方法暂停执行。

那么await是怎么做到的呢?有没有开启新线程去等待?

只有两个线程(主线程和Task开启的线程)!至于怎么做到的(我也不知道......>_<),大家有兴趣的话研究下吧!

4.IAsyncResult

IAsyncResult自.NET1.1起就有了,包含可异步操作的方法的类需要实现它,Task类就实现了该接口

在不借助于Task的情况下怎么实现异步呢?

class Program

{

static void Main(string[] args)

{

Console.WriteLine("主程序开始--------------------");

int threadId;

AsyncDemo ad = new AsyncDemo();

AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null);

Thread.Sleep(0);

Console.WriteLine("主线程线程 {0} 正在运行.",Thread.CurrentThread.ManagedThreadId)

//会阻塞线程,直到后台线程执行完毕之后,才会往下执行

result.AsyncWaitHandle.WaitOne();

Console.WriteLine("主程序在做一些事情!!!");

//获取异步执行的结果

string returnValue = caller.EndInvoke(out threadId, result);

//释放资源

result.AsyncWaitHandle.Close();

Console.WriteLine("主程序结束--------------------");

Console.Read();

}

}

public class AsyncDemo

{

//供后台线程执行的方法

public string TestMethod(int callDuration, out int threadId)

{

Console.WriteLine("测试方法开始执行.");

Thread.Sleep(callDuration);

threadId = Thread.CurrentThread.ManagedThreadId;

return String.Format("测试方法执行的时间 {0}.", callDuration.ToString());

}

}

public delegate string AsyncMethodCaller(int callDuration, out int threadId);

关键步骤就是红色字体的部分,运行结果:

和Task的用法差异不是很大!result.AsyncWaitHandle.WaitOne()就类似Task的Wait。

5.Parallel

最后说一下在循环中开启多线程的简单方法:

Stopwatch watch1 = new Stopwatch();

watch1.Start();

for (int i = 1; i <= 10; i++)

{

Console.Write(i + ",");

Thread.Sleep(1000);

}

watch1.Stop();

Console.WriteLine(watch1.Elapsed);

Stopwatch watch2 = new Stopwatch();

watch2.Start();

//会调用线程池中的线程

Parallel.For(1, 11, i =>

{

Console.WriteLine(i + ",线程ID:" + Thread.CurrentThread.ManagedThreadId);

Thread.Sleep(1000);

});

watch2.Stop();

Console.WriteLine(watch2.Elapsed);

运行结果:

循环List<T>:

List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 };

Parallel.ForEach<int>(list, n =>

{

Console.WriteLine(n);

Thread.Sleep(1000);

});

执行Action[]数组里面的方法:

Action[] actions = new Action[] {

new Action(()=>{

Console.WriteLine("方法1");

}),

new Action(()=>{

Console.WriteLine("方法2");

})

};

Parallel.Invoke(actions);

6.异步的回调

为了简洁(偷懒),文中所有Task<TResult>的返回值都是直接用task.result获取,这样如果后台任务没有执行完毕的话,主线程会等待其执行完毕。这样的话就和同步一样了,一般情况下不会这么用。简单演示一下Task回调函数的使用:

Console.WriteLine("主线程开始");

Task<string> task = Task<string>.Run(() => {

Thread.Sleep(2000);

return Thread.CurrentThread.ManagedThreadId.ToString();

});

//会等到任务执行完之后执行

task.GetAwaiter().OnCompleted(() =>

{

Console.WriteLine(task.Result);

});

Console.WriteLine("主线程结束");

Console.Read();

执行结果:

OnCompleted中的代码会在任务执行完成之后执行!

另外task.ContinueWith()也是一个重要的方法:

Console.WriteLine("主线程开始");

Task<string> task = Task<string>.Run(() => {

Thread.Sleep(2000);

return Thread.CurrentThread.ManagedThreadId.ToString();

});

task.GetAwaiter().OnCompleted(() =>

{

Console.WriteLine(task.Result);

});

task.ContinueWith(m=>{Console.WriteLine("第一个任务结束啦!我是第二个任务");});

Console.WriteLine("主线程结束");

Console.Read();

执行结果:

ContinueWith()方法可以让该后台线程继续执行新的任务。

Task的使用还是比较灵活的,大家可以研究下,好了,以上就是全部内容了,篇幅和能力都有限,希望对大家有用!

C#中 Thread,Task,Async/Await,IAsyncResult 的那些事儿![转载]的更多相关文章

  1. C#异步中的Task,async,await

    class Program { static void Main(string[] args) { Console.WriteLine("我是主线程,线程ID:{0}", Thre ...

  2. 我也来说说C#中的异步:async/await

    序 最近看了一些园友们写的有关于异步的文章,受益匪浅,写这篇文章的目的是想把自己之前看到的文章做一个总结,同时也希望通过更加通俗易懂的语言让大家了解"异步"编程. 1:什么是异步 ...

  3. Task async await

    暇之余,究多Task.async.await. using System; using System.Collections.Generic; using System.Linq; using Sys ...

  4. 基于任务的异步编程(Task,async,await)

    这节讲一下比较高级的异步编程用法Task,以及两个异步关键字async和await. Task是在C#5.0推出的语法,它是基于任务的异步编程语法,是对Thread的升级,也提供了很多API,先看一下 ...

  5. .Net Core中无处不在的Async/Await是如何提升性能的?

    一.简介 Async/Await在.Net Core中真的是无处不在,到处都是异步操作,那为什么要用?有什么作用?别人说能提升性能?网上一堆文章看的绕晕了也没说清楚, 所以这里从理论,实践,原理一个个 ...

  6. 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读

    记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...

  7. Thread,ThreadPool,Task, 到async await 的基本使用方法和理解

    很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧. ...

  8. 浅谈C#中的 async await 以及对线程相关知识的复习

    C#5.0以后新增了一个语法糖,那就是异步方法async await,之前对线程,进程方面的知识有过较为深入的学习,大概知道这个概念,我的项目中实际用到C#异步编程的场景比较少,就算要用到一般也感觉T ...

  9. 【TypeScript】如何在TypeScript中使用async/await,让你的代码更像C#。

    [TypeScript]如何在TypeScript中使用async/await,让你的代码更像C#. async/await 提到这个东西,大家应该都很熟悉.最出名的可能就是C#中的,但也有其它语言也 ...

  10. Async Await异步调用WebApi

    先铺垫一些基础知识 在 .net 4.5中出现了 Async Await关键字,配合之前版本的Task 来使得开发异步程序更为简单易控.   在使用它们之前 我们先关心下 为什么要使用它们.好比 一个 ...

随机推荐

  1. 阿里云经典网络下一键安装RouterOS-ROS系统

    1.阿里云环境centos6.9 x64: 内网网卡为eth0 外网网卡为eth1 阿里云的linux下硬盘名称为/dev/vda 注意阿里云的安全组建议开放任意协议和端口,任意IP允许访问 今天测试 ...

  2. 第一章 安装ubuntu

    最近正在研究hadoop,hbase,准备自己写一套研究的感研,下面先讲下安装ubuntu,我这个是在虚拟机下安装,先用 文件转换的方式安装. 1:选择语言:最好选择英文,以免出错的时候乱码 2:选择 ...

  3. 实现Runnable接口和继承Thread类

    如果欲创建的线程类已经有一个父类了,就不能再继承Thread类了,java不支持多继承.  实现Runnable接口: package multyThread; public class MyRuna ...

  4. 【洛谷】P1064 金明的预算方案(dp)

    题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”.今 ...

  5. IBM AIX创建lv

    #lsvg 查看当前有哪些vgrootvgvgdb02vgdb01datavg#lslv maindb_index 查看maindb_index这个lv 位于哪个vg上,新的lv也要与之相同.LOGI ...

  6. 100.64.0.0/10运营商级(Carrier-grade)NAT保留IP地址

    在一次跟踪路由的网络操作时发现自己路由器下一跳路由节点的IP地址比较奇怪,是100.64.0.1.好奇促使我查询了这个IP地址的归属,结果是保留地址,到这里觉得比较奇怪了,按照常理以IPv4为例保留的 ...

  7. 走了很多弯路的CCScrollView

    最近在学习Cocos2d-x,学习CCScrollView的时候走了很多弯路,决定记录下来. 学习cocos2d-x的最大的困惑就是资料不是很齐全,网上有很多资料,但是版本差异大,其次深度低,讲解不够 ...

  8. 面试宝典:Java面试中最高频的那20%知识点!

    Java目前已经不仅仅是一门开发语言,而是一整套生态体系. 作为一个Java程序员,既是幸运的,也是不幸的.幸运的是我们有很多轮子可以拿过来用,不幸的是我们有太多的轮子需要学习. 但是,无论是日常工作 ...

  9. 爬虫之 图片懒加载, selenium , phantomJs, 谷歌无头浏览器

    一.图片懒加载 懒加载 :    JS 代码  是页面自然滚动    window.scrollTo(0,document.body.scrollHeight)   (重点) bro.execute_ ...

  10. python学习——练习题(10)

    """ 题目:暂停一秒输出,并格式化当前时间. """ import sys import time def answer1(): &quo ...