C# Task和异步方法
本文主要参考:
https://www.cnblogs.com/qtiger/p/13497807.html
ThreadPool中有若干数量的线程。当有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。当线程池中所有的线程都被占用,又有新任务要处理时,线程池会新建一个线程来处理该任务。如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。ThreadPool相对于Thread来说可以减少线程的创建,有效减小系统开销。但是ThreadPool不能控制线程的执行顺序,也不能获取线程池内线程取消/异常/完成的通知,即不能有效监控和控制线程池中的线程。因此NET4.0在ThreadPool的基础上推出了Task。Task拥有线程池的优点,同时也解决了使用线程池不易控制的弊端。
1.无返回值的Task的创建和执行
using System;
using System.Threading.Tasks;
using System.Threading; namespace TaskDemo
{
class Program
{
static void Main(string[] args)
{
// 实例化一个Task,通过Start方法启动
Task task = new Task(
() =>
{
Thread.Sleep(1000);
Console.WriteLine($"NEW实例化一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
}
); task.Start(); // Task.Factory.StartNew(Action action)创建和启动一个Task
Task task2 = Task.Factory.StartNew(
() =>
{
Thread.Sleep(500);
Console.WriteLine($"Task.Factory.StartNew方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
}); // Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
Task task3 = Task.Run(
() =>
{
Thread.Sleep(200);
Console.WriteLine($"Task.Run方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
}); Console.WriteLine("执行主线程");
Console.Read();
}
}
}
运行结果:
2.用Task.Result获取返回值的Task的创建和执行
namespace TaskDemo
{
class Program
{
static void Main(string[] args)
{ // 有返回值的启动task
Task<string> task = new Task<string>(
() =>
{
Thread.Sleep(1000);
return $"NEW实例化一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
}
); task.Start(); // Task.Factory.StartNew(Action action)创建和启动一个Task Task<string> task2 = Task.Factory.StartNew(
() =>
{
Thread.Sleep(3000);
return $"Task.Factory.StartNew方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
}); // Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task Task<string> task3 = Task.Run(
() =>
{
Thread.Sleep(2000);
return $"Task.Run方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
}); Console.WriteLine("执行主线程");
Console.WriteLine(task.Result);
Console.WriteLine(task2.Result);
Console.WriteLine(task3.Result);
Console.Read();
}
}
}
运行结果:
可见Task.Result获取返回值时会阻塞线程。本例中,必须等到task2执行完成,获取到返回值后,才能继续执行task3。但是上面两个例子中的Task的执行都是异步的,不会阻塞主线程。
3.同步执行Task,会阻塞主线程
namespace TaskDemo
{
class Program
{
static void Main(string[] args)
{ Task task = new Task(
() =>
{
Thread.Sleep(1000);
Console.WriteLine("执行Task结束");
}
); // 同步执行,会阻碍主线程
task.RunSynchronously();
Console.WriteLine("执行主线程");
Console.Read();
}
}
}
运行结果:
4.Task的阻塞方法(Wait/WaitAll/WaitAny)
4.1Thread阻塞线程的方法
使用thread.Join()方法可阻塞主线程
namespace TaskDemo
{
class Program
{
static void Main(string[] args)
{ Thread thread1 = new Thread(
() =>
{
Thread.Sleep(1000);
Console.WriteLine("线程1执行完毕");
});
thread1.Start(); Thread thread2 = new Thread(
() =>
{
Thread.Sleep(2000);
Console.WriteLine("线程2执行完毕");
});
thread2.Start(); //阻塞主线程
thread1.Join();
thread2.Join();
Console.WriteLine("主线程执行完毕");
Console.Read();
}
}
}
运行结果:
使用Thread.Join()方法的弊端包括:
- 如果要实现很多线程的阻塞,每个线程都要调用一次Join()方法;
- 如果让所有的线程执行完毕(或任一线程执行完毕)时,立即解除阻塞,使用Join()方法不容易实现。
4.2使用Task Wait/WaitAll/WaitAny方法,实现阻塞线程
- task.Wait()表示等待task执行完毕,类似于thread.Join()
- task.WaitAll(Task[] tasks)表示只有所有的task都执行完毕再解除阻塞
- task.WaitAny(Task[] tasks)表示只要有一个task执行完毕就解除阻塞
Task task1 = new Task(
() =>
{
Thread.Sleep(1000);
Console.WriteLine("线程1执行完毕");
});
task1.Start(); Task task2 = new Task(
() =>
{
Thread.Sleep(2000);
Console.WriteLine("线程2执行完毕");
});
task2.Start(); // 阻塞主线程。task1和task2都执行完毕再执行主线程
//task1.Wait();
//task2.Wait();
Task.WaitAll(new Task[] { task1, task2 });
Console.WriteLine("主线程执行完毕");
Console.Read();
运行结果:
使用task1.Wait(); task2.Wait()可以达到同样的目的。如果把WaitAll改成WaitAny,则运行结果如下所示:
5.Task的延续操作(WhenAny/WhenAll/ContinueWith)
Wait/WaitAll/WaitAny方法返回值都是void,这些方法只是单纯的实现阻塞线程。使用WhenAny/WhenAll/ContinueWith方法可以让task执行完毕后,继续执行后续操作,这些方法执行完成返回一个task实例。
- task.WhenAll(Task[] tasks)表示所有的task都执行完毕后再去执行后续的操作
- task.WhenAny(Task[] tasks)表示任一task执行完毕后就开始执行后续操作
Task task1 = new Task(
() =>
{
Thread.Sleep(1000);
Console.WriteLine("线程1执行完毕");
});
task1.Start(); Task task2 = new Task(
() =>
{
Thread.Sleep(2000);
Console.WriteLine("线程2执行完毕");
});
task2.Start(); Task.WhenAll(new Task[] { task1, task2 }).ContinueWith(
(t) =>
{
Thread.Sleep(1000);
Console.WriteLine("执行后续操作完毕");
}); Console.WriteLine("主线程执行完毕");
Console.Read();
运行结果:
WhenAll/WhenAny方法并不会阻塞主线程。也可以使用Task.Factory.ContinueWhenAll来实现
Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>
{
Thread.Sleep(1000);
Console.WriteLine("执行后续操作完毕");
});
6.Task的任务取消(CancellationTokenSource)
6.1Thread取消任务执行
通过设置一个变量来控制任务是否停止。
bool isStop = false;
int index = 0; Thread thread1 = new Thread(
() =>
{
Console.WriteLine($"thread1的线程ID是{Thread.CurrentThread.ManagedThreadId}");
while (!isStop)
{
Thread.Sleep(1000);
Console.WriteLine($"第{++index}次执行,线程运行中...");
} }); thread1.Start();
Console.WriteLine($"主线程开始执行,主线程的ID是{Thread.CurrentThread.ManagedThreadId}");
// 5s后取消任务执行
Thread.Sleep(5000);
isStop = true;
Console.WriteLine("主线程执行完毕");
Console.Read();
运行结果:
6.2Task取消任务执行
使用专门类CancellationTokenSource来取消任务执行。
CancellationTokenSource source = new CancellationTokenSource();
int index = 0;
Task task1 = new Task(
() =>
{
while (!source.IsCancellationRequested)
{
Thread.Sleep(1000);
Console.WriteLine($"第{++index}次执行,线程运行中...");
} });
task1.Start();
Console.WriteLine("主线程开始执行");
Thread.Sleep(5000);
source.Cancel();
Console.WriteLine("主线程执行完毕");
Console.Read();
运行结果:
还可以使用source.CancelAfter(5000)实现5s后自动取消任务,即Thread.Sleep(5000); source.Cancel();这两条代码由source.CancelAfter(5000)取代。运行结果:
注意这两次运行结果中,“主线程执行完毕”的区别。也可以通过source.Token.Register(Action action)注册取消任务触发的回调函数。
CancellationTokenSource source = new CancellationTokenSource();
source.Token.Register(
() =>
{
Console.WriteLine("任务被取消后执行的操作");
});
int index = 0;
Task task1 = new Task(
() =>
{
Console.WriteLine($"task1的线程ID是{Thread.CurrentThread.ManagedThreadId}");
while (!source.IsCancellationRequested)
{
Thread.Sleep(1000);
Console.WriteLine($"第{++index}次执行,线程运行中...");
} });
task1.Start();
Console.WriteLine($"主线程开始执行,主线程的ID是{Thread.CurrentThread.ManagedThreadId}");
source.CancelAfter(5000);
Console.WriteLine("主线程执行完毕"); Console.Read();
运行结果:
7.异步方法(async/await)
async static Task<string>GetContentAsync(string fileName)
{
Console.WriteLine($"当前线程ID是{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"开始读取文件:{DateTime.Now}");
Thread.Sleep(1000);
using(StreamReader sr = new StreamReader(fileName))
{
string program = await sr.ReadToEndAsync();
Console.WriteLine($"读取文件结束:{DateTime.Now}");
return program;
}
} // 同步读取文件内容
static string GetContent(string fileName)
{
using (StreamReader sr = new StreamReader(fileName))
{
string program = sr.ReadToEnd();
return program;
}
}
static void Main(string[] args)
{
string path = @"D:\Demos\TaskDemo\postdata.txt";
Console.WriteLine($"主线程ID是{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"主程序执行开始:{DateTime.Now}");
string content = GetContentAsync(path).Result;
Console.WriteLine($"主程序输入结果:{content}");
Console.WriteLine($"主程序执行结束:{DateTime.Now}");
Console.Read();
}
运行结果:
主程序等待GetContentAsync方法执行完毕后,获取到返回值后才继续执行。这说明,如果调用方法要从调用中获取一个T类型的值,异步方法的返回类型必须是Task<T>,而且调用会获取到返回值后才会继续执行下去。如果仅仅是调用一下异步方法,不和异步方法做其他交互,则将异步方法签名返回值为void,这种调用形式也被称为“调用并忘记”。
async static void GetContentAsync(string fileName)
{
Console.WriteLine($"当前线程ID是{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"开始读取文件:{DateTime.Now}");
Thread.Sleep(1000);
using(StreamReader sr = new StreamReader(fileName))
{
string program = await sr.ReadToEndAsync();
Console.WriteLine($"读取文件结束:{DateTime.Now}");
}
}
static void Main(string[] args)
{
string path = @"D:\Demos\TaskDemo\postdata.txt";
Console.WriteLine($"主线程ID是{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"主程序执行开始:{DateTime.Now}");
GetContentAsync(path);
Console.WriteLine($"主程序执行结束:{DateTime.Now}");
Console.Read();
}
运行结果:
C# Task和异步方法的更多相关文章
- BeginInvoke、ThreadPool、Task三类异步方法的区别和速度比较
速度(最快为1) 返回值 多参数 等待在时限内完成 超时后结束 ThreadPool.UnsafeQueueUserWorkItem() 1 非原生支持1 非原生支持 非原生支持3 不支持 Thr ...
- C# 将 Begin 和 End 异步方法转 task 异步
在 .NET Framework 有两个不同的异步方法,一个是 Asynchronous Programming Model (APM) 另一个是 Task-based asynchronous pa ...
- 使用Task简化Silverlight调用Wcf
原文http://www.cnblogs.com/lemontea/archive/2012/12/09/2810549.html 从.Net4.0开始,.Net提供了一个Task类来封装一个异步操作 ...
- C# 异步方法(AM)
Ø 前言 C# Asynchronous Programming(异步编程)有几种实现方式,其中 Asynchronous Method(异步方法)就是其中的一种.异步方法是 C#5.0 才有的新特 ...
- C#中如果用await关键字来await一个为null的Task对象会抛出异常
await & async模式是C#中一个很重要的特性,可以用来提高异步程序(多线程程序)的执行效率.但是如果尝试用await关键字来await一个为null的Task对象,会导致程序抛出Nu ...
- SpringBoot中Async异步方法和定时任务介绍
1.功能说明 Spring提供了Async注解来实现方法的异步调用. 即当调用Async标识的方法时,调用线程不会等待被调用方法执行完成即返回继续执行以下操作,而被调用的方法则会启动一个独立线程来执行 ...
- C# 5.0 新特性之异步方法(AM)
Ø 前言 C# Asynchronous Programming(异步编程)有几种实现方式,其中 Asynchronous Method(异步方法)就是其中的一种.异步方法是 C#5.0 才有的新特 ...
- [翻译]C#中异步方法的性能特点
翻译自一篇博文,原文:The performance characteristics of async methods in C# 异步系列 剖析C#中的异步方法 扩展C#中的异步方法 C#中异步方法 ...
- [翻译]扩展C#中的异步方法
翻译自一篇博文,原文:Extending the async methods in C# 异步系列 剖析C#中的异步方法 扩展C#中的异步方法 C#中异步方法的性能特点. 用一个用户场景来掌握它们 在 ...
随机推荐
- /etc/crontab和crontab -e的区别
(1) /etc/crontab是系统级别的crontab,系统的设置等,这种方法只有root用户能用 crontab -e是用户级的crontab,会被写到 /var/spool/cron 目录下, ...
- MySQL单表查询(分组-筛选-过滤-去重-排序)
目录 一:单表查询 1.单表查询(前期准备) 2.插入记录(写入数据) 3.查询关键字 二:查询关键字之where 1.查询id大于等于3小于等于6的数据 2.查询薪资是20000或者18000或者1 ...
- Codeforces Round #741 (Div. 2)
全部题目跳转链接 A - The Miracle and the Sleeper 题意 给定\([l, r]\) 求出在这个区间内的两个数字a和b的取模的最大值 (\(a \ge b\)) 分析 上届 ...
- kicad6 封装库的管理
kicad6 封装库的管理 kicad6 的封装编辑器有很多莫名其妙的地方, 让人在第一次用的时候摸不着头脑. 在下面稍微总结一下封装库的操作 1. 封装库的创建 选择 文件 -> 新建库 有两 ...
- mac每次打开终端都需要source ~/.bashrc以及~/.bash_profile问题
问题描述 在学习git的时候在~/.bashrc下面配置了git log命令的别名 #用于输出git提交日志 alias git-log='git log --pretty=oneline --all ...
- VC 获取当前运行窗口名称
转载请注明来源:https://www.cnblogs.com/hookjc/ BOOL CALLBACK WindowChild(HWND hwnd,LPARAM lparam){ CFGDlg* ...
- js Array.prototype.slice.call(arguments,0) 理解
Array.prototype.slice.call(arguments,0) 经常会看到这段代码用来处理函数的参数 网上很多复制粘帖说:Array.prototype.slice.call(argu ...
- python日志装饰器实现
问题出自:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143184355 ...
- 内联函数 在ios中的运用 --黄仁斌
定义: 有函数的结构,但不具备函数的性质,类似于宏替换.代码中使用inline定义,能否形成内联函数,还要看编译器对内联函数体内部的定义的具体处理.产生的动机: 消除函数调用产生的开销 ...
- mac os 利用ssh 搭建git server服务器详细教程,以及git基本用法
详细讲mac 连接mac的git操作 首先在服务端上 第一:新建一个仓库 1, cd /Users/userName/projects 用linux命令进入一个你想要创建与他人共享的文件夹. 2,su ...