C#基础教程(八)异步编程

说到异步编程,前一章的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,void仅仅起到"调用并忘记",不需要与调用者交互可使用void,即使有return也不会返回任何东西;Task,如果不需要从异步方法返回某个值,但调用者想检查被调异步方法状态,可以返回一个Task,即使有return,也不会返回任何东西。Task,如果想从异步方法中返回一个T型值,可以用这个返回类型,值在Task.Result属性中。很简单的概念,例子不讲了。

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);
}

注意回调函数的形参,是一个接口形式的引用,但又可以转过去。

 

 

你可能感兴趣的:(C#)