C#_异步编程

理解异步

同步任务(一般指方法):一个应用程序调用某个方法,等到其执行完才进行下一步操作。

异步任务(一般指方法):一个程序调用某一方法,在处理完成前就返回该方法,不需要等待方法完成就可以继续执行后面的代码。

异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。

异步方法

微软极力推荐基于Task任务的async和await关键字实现方法的异步调用,也称为基于任务的异步模式,简称TAP(Task-base asynchronous pattern)。C#5.0中的async和await关键字只是编译器功能,C#编译器在内部还是会使用Task类创建代码的。

异步方法定义

方法上要有async关键字,放在访问修饰符后面。

方法名最好以Async结尾,表示这是一个异步方法,从方法名就能识别出来。

方法的返回值类型只能是3种:Task、Task< T >、void。

方法体代码中要包含1个或多个await表达式,表示需要异步执行的任务,如果方法体中不存在await表达式,则与普通方法一样执行。await后面跟一个Task.Run(…)或者直接调用其他的一个异步方法。

        public async void Method1Async()
        {
            await Task.Run(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    Console.WriteLine($"I am Method1Async {Thread.CurrentThread.ManagedThreadId}");
                }
            });
        }

        public async Task Method2Async()
        {
            await Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine($"I am Method2Async  {Thread.CurrentThread.ManagedThreadId}");
                }
            });
        }

        public async Task<string> Method3Async()
        {
            string res = await Task<string>.Run(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    Console.WriteLine($"I am Method3Async  {Thread.CurrentThread.ManagedThreadId}");
                }
                return "I am Method3Async";
            });
            return res;
        }

异步方法调用

调用void返回类型的异步方法

    static void Main(string[] args)
    {
        Foo foo = new Foo();

        Console.WriteLine($"begin Method1Async {Thread.CurrentThread.ManagedThreadId}");
        foo.Method1Async();//执行到这句的时候不会阻塞,而是会继续执行下面的代码
        Console.WriteLine($"end Method1Async {Thread.CurrentThread.ManagedThreadId}");
    }
output:
begin Method1Async 1
I am Method1Async CurrentThread:4 0
I am Method1Async CurrentThread:4 1
I am Method1Async CurrentThread:4 2
I am Method1Async CurrentThread:4 3
I am Method1Async CurrentThread:4 4
I am Method1Async CurrentThread:4 5
I am Method1Async CurrentThread:4 6
I am Method1Async CurrentThread:4 7
I am Method1Async CurrentThread:4 8
I am Method1Async CurrentThread:4 9
end Method1Async 1
I am Method1Async CurrentThread:4 10
I am Method1Async CurrentThread:4 11
I am Method1Async CurrentThread:4 12
I am Method1Async CurrentThread:4 13
I am Method1Async CurrentThread:4 14
I am Method1Async CurrentThread:4 15
I am Method1Async CurrentThread:4 16
I am Method1Async CurrentThread:4 17
I am Method1Async CurrentThread:4 18
I am Method1Async CurrentThread:4 19
I am Method1Async CurrentThread:4 20
I am Method1Async CurrentThread:4 21
I am Method1Async CurrentThread:4 22
I am Method1Async CurrentThread:4 23
I am Method1Async CurrentThread:4 24
I am Method1Async CurrentThread:4 25
I am Method1Async CurrentThread:4 26
I am Method1Async CurrentThread:4 27
I am Method1Async CurrentThread:4 28

我们可以看到,异步方法中的打印内容还没有执行100次就结束了,这是因为在执行异步方法的时候不会阻塞,而是会继续执行异步方法后面的代码,所以我们看到“end Method1Async 1”是在中间打印的。这句话打印完了之后Main方法就执行完毕了,程序结束,所以最终异步方法没有执行完毕就终止了。建议在正式项目中不要使用void返回类型的异步方法。

调用Task返回类型的异步方法

        static void Main(string[] args)
        {
            Foo foo = new Foo();
            
            Console.WriteLine($"begin Method2Async {Thread.CurrentThread.ManagedThreadId}");
            Task task = foo.Method2Async();//异步方法不会阻塞 会立刻执行下面的代码

            Console.WriteLine($"Task Id:{task.Id}  Task Status:{task.Status}");
            task.Wait();//在这里会进行无限等待,但是主线程是释放的,也不会阻塞(控制台还可以移动),只到待异步方法的完成后继续执行下面的代码。
            //task.Wait(1000);//只等待10000ms,即使异步方法没有结束也会继续执行下面的代码
            Console.WriteLine($"Task Id:{task.Id}  Task Status:{task.Status}");//可以看看异步方法结束后的状态

            Console.WriteLine($"end Method2Async {Thread.CurrentThread.ManagedThreadId}");
        }
output:
begin Method2Async 1
I am Method2Async CurrentThread:4 0
Task Id:2  Task Status:WaitingForActivation
I am Method2Async CurrentThread:4 1
I am Method2Async CurrentThread:4 2
I am Method2Async CurrentThread:4 3
I am Method2Async CurrentThread:4 4
I am Method2Async CurrentThread:4 5
I am Method2Async CurrentThread:4 6
I am Method2Async CurrentThread:4 7
I am Method2Async CurrentThread:4 8
I am Method2Async CurrentThread:4 9
Task Id:2  Task Status:RanToCompletion
end Method2Async 1

由于返回的是Task类型,所以我们可以获取与任务相关的信息(Status、Id等信息),甚至可以执行Wait方法、WaitAny和WaitAll等方法。

值得一提的是Wait()方法,如果需要等待异步方法执行完毕则需要调用Wait()方法。

调用Task返回类型的异步方法

        static void Main(string[] args)
        {
            Foo foo = new Foo();
            
            Console.WriteLine($"begin Method1Async {Thread.CurrentThread.ManagedThreadId}");
            Task<string> task = foo.Method3Async();
            Console.WriteLine(task.Result);//如果没有在异步方法没有完成的时候调用Result属性,那么在此处会自动Wait(),直到异步方法执行完成。

            //task.Wait();//在这里会进行无限等待,但是主线程是释放的,也不会阻塞(控制台还可以移动),只到待异步方法的完成后继续执行下面的代码。
            //task.Wait(1000);//只等待10000ms,即使异步方法没有结束也会继续执行下面的代码
            Console.WriteLine($"Task Id:{task.Id}  Task Status:{task.Status}");//可以看看异步方法结束后的状态

            Console.WriteLine($"end Method3Async {Thread.CurrentThread.ManagedThreadId}");
        }

Task中T类型就是Result属性的类型,也就是异步方法中return的类型。如果在异步方法执行过程中调用Result属性,那么就相当于调用了Wait()方法,再调用Result属性得到异步方法的返回结果。

值得一提的是Task类继承自Task类型,所以T在ask类中的属性和方法,Task对象也可以直接调用。

异步方法中就是简单新开了一个线程吗?

要回答上述问题,我们将第二个异步方法修改为如下:

        public async Task Method2Async()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"before TaskRun CurrentThread:{Thread.CurrentThread.ManagedThreadId} {i}");
            }

            await Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine($"I am Method2Async CurrentThread:{Thread.CurrentThread.ManagedThreadId} {i}");
                }
            });

            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"after TaskRun CurrentThread:{Thread.CurrentThread.ManagedThreadId} {i}");
            }
        }

在Main方法中执行如下:

        static void Main(string[] args)
        {
            Foo foo = new Foo();
            Console.WriteLine($"begin Method2Async CurrentThread:{Thread.CurrentThread.ManagedThreadId}");
            Task task = foo.Method2Async();//异步方法不会阻塞 会立刻执行下面的代码

            Console.WriteLine($"Task Id:{task.Id}  Task Status:{task.Status}");
            task.Wait();//在这里会进行无限等待,但是主线程是释放的,也不会阻塞(控制台还可以移动),只到待异步方法的完成后继续执行下面的代码。
            //task.Wait(1000);//只等待10000ms,即使异步方法没有结束也会继续执行下面的代码
            Console.WriteLine($"Task Id:{task.Id}  Task Status:{task.Status}");//可以看看异步方法结束后的状态

            Console.WriteLine($"end Method2Async CurrentThread:{Thread.CurrentThread.ManagedThreadId}");
        }

打印结果为:

output:
begin Method2Async CurrentThread:1
before TaskRun CurrentThread:1 0
before TaskRun CurrentThread:1 1
before TaskRun CurrentThread:1 2
before TaskRun CurrentThread:1 3
before TaskRun CurrentThread:1 4
before TaskRun CurrentThread:1 5
before TaskRun CurrentThread:1 6
before TaskRun CurrentThread:1 7
before TaskRun CurrentThread:1 8
before TaskRun CurrentThread:1 9
Task Id:2  Task Status:WaitingForActivation
I am Method2Async CurrentThread:4 0
I am Method2Async CurrentThread:4 1
I am Method2Async CurrentThread:4 2
I am Method2Async CurrentThread:4 3
I am Method2Async CurrentThread:4 4
I am Method2Async CurrentThread:4 5
I am Method2Async CurrentThread:4 6
I am Method2Async CurrentThread:4 7
I am Method2Async CurrentThread:4 8
I am Method2Async CurrentThread:4 9
after TaskRun CurrentThread:4 0
after TaskRun CurrentThread:4 1
after TaskRun CurrentThread:4 2
after TaskRun CurrentThread:4 3
after TaskRun CurrentThread:4 4
after TaskRun CurrentThread:4 5
after TaskRun CurrentThread:4 6
after TaskRun CurrentThread:4 7
after TaskRun CurrentThread:4 8
after TaskRun CurrentThread:4 9
Task Id:2  Task Status:RanToCompletion
end Method2Async CurrentThread:1

我们可以看到,在异步方法中:await之前的代码块与调用线程相同的,await中的代码块是开启了一个任务,会在线程池中去取一个线程执行这个任务,但是await之后的代码块是线程池中的线程相同的,并不是调用线程。。。。

在遇到awiat关键字之前,程序是按照代码顺序自上而下以同步方式执行的。
在遇到await关键字之后,系统做了以下工作:

  1. 异步方法将被挂起
  2. 将控制权返回给调用者
  3. 使用线程池中的线程(而非额外创建新的线程)来计算await表达式的结果,所以await不会造成程序的阻塞
  4. 完成对await表达式的计算之后,若await表达式后面还有代码则由执行await表达式的线程(不是调用方所在的线程)继续执行这些代码

所以,在调用:

Task task = foo.Method2Async();

后,并不会立刻执行下面的代码块,而是进入异步方法中,执行await表达式之前的代码块(所以我们看到执行这一块代码的线程与调用线程相同),直到遇到await表达式后,异步方法将被挂起,才会将控制权返回给调用线程,继续进行调用线程中的代码。同时,会使用线程池中的线程(并不是创建新的线程)来执行await表达式,所以不会造成程序的阻塞。
当该线程执行完await表达式后,若后面还有代码,则由该线程继续执行。(所以我们看到执行这一块的线程与await表达式中的线程相同)

所以,很多地方可以看到以下的写法:

        public async Task Method2Async()
        {
            await Task.Yield();//可等待的空任务,所以await后的语句,都是线程池中调度的线程执行的,并不是调用异步方法的线程执行的。
            //就是一个切换线程的操作。。。

            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"Method2Async Run CurrentThread:{Thread.CurrentThread.ManagedThreadId} {i}");
            }
        }

就是通过await Task.Yield()来切换线程。

阻塞非阻塞与同步异步的区别

同步和异步

同步和异步关注的是消息通信机制。所谓同步,就是在发出一个调用后主动等待这个调用的结果。

而异步则相反,调用在发出之后,不会立刻的到结果,而是在调用发出后,被调用者通过状态、通知来通知调用者,或者通过回调函数处理这个调用。

阻塞和非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值