C#中 Thread,Task,Async/Await,IAsyncResult 的那些事儿!

说起异步,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# 异步和多线程的更多相关文章

  1. IOS异步和多线程操作&&在sqlite3中的应用

    1,数据库I/O操作(异步) 数据库本身是存储在磁盘上.访问和修改数据库,即对磁盘进行读写,即I/O操作. 磁盘属于计算机硬件,具有DMA能力,不需要CPU干预,可以实现异步操作. I/O操作一般是消 ...

  2. C#中异步和多线程的区别

    C#中异步和多线程的区别是什么呢?异步和多线程两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性.甚至有些时候我们就认为异步和多线程是等同的概念.但是,异步和多线程还是有一些区别的.而这些区 ...

  3. PHP中实现异步调用多线程程序代码

    本文章详细的介绍了关于PHP中实现异步调用多线程方法,下面我们以给1000个用户发送一封推荐邮件,用户输入或者导入邮件账号了提交服务器执行发送来讲述. 比如现在有一个场景,给1000个用户发送一封推荐 ...

  4. c#异步和多线程有什么区别和联系?

    异步和多线程可以说没有必然的联系,只能说异步可以通过多线程实现而已要理解这些东西,你得具备很多相关的知识,操作系统原理,编译原理等简单地来说,计算机或者说CPU执行你的代码都是顺序执行的,当前的语句没 ...

  5. 编写高质量代码改善C#程序的157个建议——建议71:区分异步和多线程应用场景

    建议71:区分异步和多线程应用场景 初学者有时候会将异步和多线程混为一谈.如果对它们之间的区别不是很清楚,很容易写出下面这样的代码: private void buttonGetPage_Click( ...

  6. C#用委托实现异步,异步与多线程的异同

    异步与多线程的区别(转) 一.异步和多线程有什么区别?其实,异步是目的,而多线程是实现这个目的的方法.异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作就没有必要异步了),可以继续自 ...

  7. 【Java_基础】并发、并行、同步、异步、多线程的区别

    1. 并发:位于同一个处理器上的多个已开启未完成的线程,在任意一时刻系统调度只能让一个线程获得CPU资源运行,虽然这种调度机制有多种形式(大多数是以时间片轮巡为主).但无论如何,都是通过不断切换需要运 ...

  8. C#中的异步和多线程

    许多开发人员对异步代码和多线程以及它们的工作原理和使用方法都有错误的认识.在这里,你将了解这两个概念之间的区别,并使用c#实现它们. 我:"服务员,这是我第一次来这家餐厅.通常需要4个小时才 ...

  9. C#异步和多线程以及Thread、ThreadPool、Task区别和使用方法

    本文的目的是为了让大家了解什么是异步?什么是多线程?如何实现多线程?对于当前C#当中三种实现多线程的方法如何实现和使用?什么情景下选用哪一技术更好? 第一部分主要介绍在C#中异步(async/awai ...

  10. 异步和多线程,委托异步调用,Thread,ThreadPool,Task,Parallel,CancellationTokenSource

    1 进程-线程-多线程,同步和异步2 异步使用和回调3 异步参数4 异步等待5 异步返回值 5 多线程的特点:不卡主线程.速度快.无序性7 thread:线程等待,回调,前台线程/后台线程, 8 th ...

随机推荐

  1. Datasets for MachineLearning

    Public datasets for machine learning  http://homepages.inf.ed.ac.uk/rbf/IAPR/researchers/MLPAGES/mld ...

  2. CYPRESS USB芯片win10驱动

    The ZIP file attached with this knowledge base article contains the CyUSB3.inf and CyUSB3.sys files ...

  3. IOS日期转为今天昨天形式

    近期项目有类似QQ空间展示动态的UI,模仿了QQ空间的时间显示.在此记录,以备查阅. 这是QQ空间的ui: 时间显示为: 1.今天-->今天 xx:xx(今天 15:39) 2.昨天--> ...

  4. windows ffmpeg 的安装

    本文我们要安装的是 windows 下的 ffmpeg 命令行工具,安装的步骤十分简单,分为:下载.解压.配置环境变量. 下载,进入 http://ffmpeg.org/download.html#b ...

  5. C#无符号右移

    /// <summary>         /// 无符号右移,与JS中的>>>等价         /// </summary>         /// & ...

  6. HDU 1284 钱币兑换问题 母函数、DP

    题目链接:HDU 1284 钱币兑换问题 钱币兑换问题 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (J ...

  7. 【2024】求X到Y之间的整数和

    Time Limit: 3 second Memory Limit: 2 MB [问题描述] 计算X到Y之间的整数和(要求用函数实现).注意输入时X不一定小于Y,且X.Y不一定都是整数. [输入] 两 ...

  8. java生成6位随机数

    生成6位随机数(不会是5位或者7位,仅只有6位): System.out.println((int)((Math.random()*9+1)*100000)); 同理,生成5位随机数: System. ...

  9. Drupal 7 模块开发 建立模块帮助信息(hook_help)

    建立模块请參考 <Drupal 7 模块开发 建立> 假设你要支持中文,文件格式必须保存为 UTF-8.NO BOM ------------------------------ hook ...

  10. Android Notification如何显示表情?

    遇到这种分析用什么实现的,肯定要祭出大杀器Android Device Monitor(AS在Tools->Android)打开之后,选中连接的设备,然后点击小手机图标,即可导出UI层次图.咱们 ...