说到异步编程,前一章的Task内容也是异步编程,这篇主要讲讲.net5.0中的新异步形式——异步方法及委托形式的异步调用。
(一)异步方法
async,await是异步方法的主要语法标志,两者的关系:首先一个被标记为async的方法,可以没有await调用,只不过会有编译警告。这是很显然的,不是把一个方法标记成async这个方法就成了异步调用的方法了。async这个关键词其实反而是可以省略的,这个关键词存在的意义是为了向下兼容,为await提供上下文,主要是await表达式开启了异步,我们通过下面一个异步方法控制流例子,可以来细致理解一下。
static void Main(string[] args)
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine("Main1 tid {0}", tid);
Task t = CalAsync();
Console.WriteLine("Main after CalAsync");
Console.Read();
}
public static async Task CalAsync()
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine("CalAsync1 tid {0}", tid); //————wait表达式之前部分
int result = await Task.Run(new Func(Cal)); //————await表达式
tid = Thread.CurrentThread.ManagedThreadId.ToString(); //————后续部分
Console.WriteLine("CalAsync2 tid {0}, result={1}", tid, result);
return result;
}
public static int Cal()
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine("Cal tid {0}", tid);
int sum = 0;
for (int i = 0; i < 999; i++)
{
sum = sum + i;
}
Console.WriteLine("sum={0}", sum);
return sum;
}
结果:
Main1 tid 1
CalAsync1 tid 1
Main after CalAsync
Cal tid 3
sum=498501
CalAsync2 tid 3, result=498501
我们把一个异步方法分成了3个部分,await前、中和后续三个部分,await前部分和调用线程同一个1,await表达式开启任务等待调度器安排另一线程3,后续部分与await部分同一线程3,顺序执行。这个控制流至关重要的,是开启异步方法的大门,所以我安排在开头部分。
1.返回类型
异步方法有void、Task和Task
2.取消异步
与Task一样,通过CancellationTokenSource"协同"过程来中断,不讲。
3.同步等待任务、异步方法中异步等待任务
学过前一章Task,我们了解WaitAll,WhenAll的区别,两者意思都是等待任务所有完成,但又有所有别,一个会阻塞线程一个不会。同步等待,当调用线程中使用Task的wait()、WaitAll()等函数,就可阻塞线程实现等待,或者在调用线程中获取异步方法返回过来的值,也会实现阻塞,跟前一章Task机制差不多。异步方法中异步等待,我简写一下
private async Task CountCharactersAsync()
{
Task task1 = new Task(() => { });
Task task2 = new Task(() => { });
Task[] tasks=new Task[]{task1,task2};
await Task.WhenAll(tasks).ContinueWith((s)=>{});//WhenAll开启任务,跟Task.Run()一样道理
Console.WriteLine("所有任务结束");
return 0;
}
4.Delay方法
同样属于延迟肯定会与Thread.Sleep()函数作比较,Task.Delay()属于异步延迟,官方解释是创建将在时间延迟后完成的任务。后者属于同步延迟,阻塞线程若干时间。
static void Main(string[] args)
{
Task task = CALAsync();
Console.WriteLine("helloword");
Console.ReadKey();
}
public static async Task CALAsync()
{
string tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine("CalAsync2 tid {0}", tid);
await Task.Delay(3000)//可ContinueWith()
tid = Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine("CalAsync2 tid {0}", tid);//线程改变,3s后才输出
}
结果
CalAsync2 tid 1
helloword
CalAsync2 tid 4
5.Yield方法
Task.Yield() 简单来说就是创建时就已经完成的 Task ,或者说执行时间为0的 Task ,或者说是空任务,也就是在创建时就将 Task 的 IsCompeted 值设置为0。难道没事去创建个空任务干什么?Task.Yield 产生的空任务仅仅是为 await 做嫁衣,而真正的图谋是借助 await 实现线程的切换,让 await 之后的操作重新排队从线程池中申请线程继续执行。
static class DoStuff
{
public static async Task FindSeriesSum(int i1)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
int sum = 0;
for (int i = 0; i < i1; i++)
{
sum += i;
if (i % 1000 == 0)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());//线程id会改变
await Task.Yield();//创建线程什么都没干,在这里线程id不停的变,在使用不同的线程
}
}
return sum;
}
}
static void Main(string[] args)
{
Task value = DoStuff.FindSeriesSum(1000000);
CountBig(100000);
CountBig(100000);
CountBig(100000);
CountBig(100000);
Console.WriteLine("sum:{0}", value.Result);
}
private static void CountBig(int p)
{
for (int i = 0; i < p; i++)
{
}
}
(二)并行循环
Parallel提供了数据和任务并行性,有10多种重载函数,Parallel也是多线程,具体机制没研究。
static void Main(string[] args)
{
List lsts=new List();
Parallel.For(0, 1000000, (s) => { lsts.Add(s*s);});
Console.ReadKey();
}
//并行其实也是多线程在执行任务,有别于并发,注意几点,1.无序2.集合线程安全3.用锁竞争的时候时间反而更加浪费
(三)委托异步
主要函数是BeginInvoke和EndInvoke,EndInvoke用来等待获取返回值结果。
static void Main(string[] args)
{
Func del = new Func(() => { Thread.Sleep(5000); Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); return 5; });
IAsyncResult asyn=del.BeginInvoke(null,null);
int result = del.EndInvoke(asyn);//等待返回值
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
IAsyncResult这个接口是关于一些线程的消息,AsyncResult是这个接口的实现类。委托形式的异步一般有三种状态:一直等待状态、轮询状态和回调模式。上述例子就是一直等待模式,轮询模式是通过IAsyncResult状态信息IsCompleted来不断询问委托线程是否结束,它是可以进行主线程的工作的,就是一个while(asyn.IsCompleted){}的过程。重点说说回调模式,前两种方式我们可以发现,都是在调用线程来处理异步结果,回调模式就是可以在委托异步中处理结果,不需要调用线程去做任何事,可以放心地"不用管"。
static void Main(string[] args)
{
Func del = new Func(() => { Thread.Sleep(5000);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); return 5; });
IAsyncResult asyn = del.BeginInvoke(new AsyncCallback(callBack), null);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
static void callBack(IAsyncResult t)
{
string type = t.GetType().Name;
AsyncResult asyn = t as AsyncResult;
Func x = (Func)asyn.AsyncDelegate;
int c = x.EndInvoke(t);
}
注意回调函数的形参,是一个接口形式的引用,但又可以转过去。