5.1 async异步关键字
thread->threadpool(APM,EAP)->TPL->异步函数 asynchronous function,使用async关键字标注一个方法。
async Task<string> GetStringAsync(){
await Task.Delay(TimeSpan.FromSeconds(2));
return "Albert";
}
如果方法本省无需异步或并行运行,标记为async无意义,async方法有显著的性能损失,比不带该关键字的方法慢上40-50倍。
5.2 使用await操作符获取异步任务结果
async同步方法,有此关键字,在方法内部才能使用await等待异步执行结果,await会去等待后面的Task执行完毕并返回结果。
namespace AsyncAndAwait {
class Program {
static void Main(string[] args) {
CalcSomeOperate();
Thread.Sleep(5);
Console.ReadLine();
}
static async Task<string> CalcSomeOperate() {
Console.WriteLine("======开启异步方法=======");
string result = await Task<string>.Run(() => { return $"helloworld {Thread.CurrentThread.ManagedThreadId}"; });
Console.WriteLine($"我是程序运行结果:{result}");
await Task.Run(()=>Console.WriteLine($"Hello {Thread.CurrentThread.ManagedThreadId}"));
return result;
}
}
}
5.3 在Lambda表达式中使用await操作符
这边尝试了C#6.0引入的自动属性初始化以及C#7.0引入的元组概念(swift里面也有)
在AsynchronousProcessing()方法里面,首先声明了一个Func委托,输入参数string类型,输出参数Task
namespace LambdaAwait {
class Program {
public int TestValue { get; set; } = 3; //C#6.0 .NET Framework4.6引入
static void Main(string[] args) {
var test = TestMethod();
Console.WriteLine(test.Age);
Console.WriteLine(test.Name);
Task t = AsynchronousProcessing();
t.Wait();
Console.ReadLine();
}
//C#7.0 元组,支持直接解析(int a,string b)=TestMethod
static (int Age,string Name) TestMethod() {
return (24,"Albert");
}
//Func<输入参数,输入参数...输出参数>
static async Task AsynchronousProcessing() {
Func<string, Task<string>> asyncLambda = async name => {
await Task.Delay(TimeSpan.FromSeconds(2));
return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}" +
$"Is thread pool:{CurrentThread.IsThreadPoolThread}";
};
string result = await asyncLambda("async lambda");
Console.WriteLine(result);
}
}
}
5.4 对连续的异步任务使用
TPL对于连续任务,首先是创建一个容器任务,而后执行延续任务,通过延续任务来执行多任务。异步函数通过await加 async声明的函数直接进入任务模式,要注意的是异步并不总是并行执行。
namespace ContinuousAwait {
class Program {
static void Main(string[] args) {
Task t = AsynchronyWithTPL();
t.Wait();
t = AsynchronyWithWait();
t.Wait();
Console.ReadLine();
}
static Task AsynchronyWithTPL() {
var containerTask = new Task(() => {
Task<string> t = GetInfoAsync("TPL1");
t.ContinueWith(task => {
Console.WriteLine(t.Result);
Task<string> t2 = GetInfoAsync("TPL2");
t2.ContinueWith(innerTask => Console.WriteLine(innerTask.Result),
TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);
}, TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.AttachedToParent);
});
containerTask.Start();
return containerTask;
}
static async Task AsynchronyWithWait() {
try {
//异步并不总是并行执行,await顺序执行。
string result = await GetInfoAsync("Async 1");
Console.WriteLine(result);
result = await GetInfoAsync("Async 2");
Console.WriteLine(result);
}
catch (Exception ex) {
Console.WriteLine(ex.Message); ;
}
}
static async Task<string> GetInfoAsync(string name) {
Console.WriteLine($"Task {name} started.");
await Task.Delay(TimeSpan.FromSeconds(2));
if(name == "TPL2") {
throw new Exception("Boom!");
}
return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}" +
$"Is thread pool thread:{CurrentThread.IsThreadPoolThread}";
}
}
}
5.5 对并行执行的异步任务使用await操作符
并行执行,通过Task.WhenAll来组合任务。这里需要注意的是尽量使用await Task.Delay来设置延时,而非Thread.Sleep方法,第一种方式可以有效减少线程的消费,是创建高伸缩性的服务器程序的关键。
namespace ParallelAsyncTask {
class Program {
static void Main(string[] args) { //C#6.0支持在main方法中加async了
MainMethod();
Console.ReadLine();
}
static async Task<string> GetInfoAsync(string name) {
Console.WriteLine($"Task {name} started.");
await Task.Delay(TimeSpan.FromSeconds(2));//启动定时器,等定时器结束调用线程池里的线程
return $"Task {name} is running on a thread id {CurrentThread.ManagedThreadId}" +
$"Is thread pool thread:{CurrentThread.IsThreadPoolThread}";
}
static async Task MainMethod() {
Task<string> t1 = GetInfoAsync("Task1");
Task<string> t2 = GetInfoAsync("Task2");
string[] result = await Task.WhenAll(t1, t2);
foreach (var item in result) {
Console.WriteLine(item);
}
Console.ReadLine();
}
}
}
5.6 ConfigureAwait(continueOnCapturedContext:false)
//ConfigureAwait(true) 代码由同步执行进入异步执行时,当前同步执行的线程上下文信息会被
//捕捉并保存到SynchronizaitonContext中,供异步执行中使用,在异步执行完成后(await之后的代码)
//的同步执行中使用,在await之后虽然是同步执行,但是发生了线程切换,产生了开销,但此开销很小,几乎
//不会影响性能问题
//ConfigureAwait(flase)不进行线程上下文信息的捕捉,async方法中与await之后的代码执行时就无法获取await
//之前的线程的上下文信息
await捕捉同步上下文,会有一定的线程切换资源消耗,同步上下文需要3s,不同步2ms
namespace WinformAvoidSynchronousContent
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "Calculating....";
TimeSpan resultWithContent = await Test();
TimeSpan resultWithoutContent = await TestNoContext();
//从执行结果很明显,不进行上下文同步的方式性能开销更少。
this.textBox1.AppendText($"With the context:{resultWithContent}\n");
this.textBox1.AppendText($"Without the context:{resultWithoutContent}\n");
}
static async Task<TimeSpan> Test()
{
const int iterationsNumber = 100000;
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < iterationsNumber; i++)
{
var t = Task.Run(() => { });
//ConfigureAwait(true) 代码由同步执行进入异步执行时,当前同步执行的线程上下文信息会被
//捕捉并保存到SynchronizaitonContext中,供异步执行中使用,在异步执行完成后(await之后的代码)
//的同步执行中使用,在await之后虽然是同步执行,但是发生了线程切换,产生了开销,但此开销很小,几乎
//不会影响性能问题
//ConfigureAwait(flase)不进行线程上下文信息的捕捉,async方法中与await之后的代码执行时就无法获取await
//之前的线程的上下文信息
await t;
}
sw.Stop();
return sw.Elapsed;
}
static async Task<TimeSpan> TestNoContext()
{
const int iterationsNumber = 100000;
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < iterationsNumber; i++)
{
var t = Task.Run(() => { });
//ConfigureAwait(true) 代码由同步执行进入异步执行时,当前同步执行的线程上下文信息会被
//捕捉并保存到SynchronizaitonContext中,供异步执行中使用,在异步执行完成后(await之后的代码)
//的同步执行中使用,在await之后虽然是同步执行,但是发生了线程切换,产生了开销,但此开销很小,几乎
//不会影响性能问题
//ConfigureAwait(flase)不进行线程上下文信息的捕捉,async方法中与await之后的代码执行时就无法获取await
//之前的线程的上下文信息
await t.ConfigureAwait(continueOnCapturedContext: false);
}
sw.Stop();
return sw.Elapsed;
}
}
}
5.7 对动态类型使用await
使用NuGet包安装ImpromptuInterface 一个轻量级的C#动态框架
ImpromptuInterface轻松将动态对象和接口绑定起来,从而直接利用接口中提供的属性和方法。
使用示例程序:
namespace ImpromptuExample {
class Program {
static void Main(string[] args) {
dynamic expando = new ExpandoObject();
expando.Name = "Albert";
expando.Age = "24";
expando.GetAge = (Func<int>)(() => { return 24; });
expando.Prop1 = "WPF";
//Improptu.ActLike动态实现继承接口的对象
IMyInterface myInterface = Impromptu.ActLike(expando);
Console.WriteLine(expando.Age);
Console.WriteLine(expando.GetAge);
Console.WriteLine(myInterface.Prop1);
Console.ReadLine();
}
}
public interface IMyInterface {
string Prop1 { get; }
long Prop2 { get; }
Guid Prop3 { get; }
bool Meth1(int x);
}
}