C#中的async和await的使用详解

C#中的async和await的使用详解

  1. 简单介绍
  • 在C#中,在很多的时候,都需要一些异步操作,并且在做完这些异步操作之后,可以在后面接着做一些处理,在 .net framework 4.5之后,便加入了一种简洁的方式来使用异步操作,这就是async和await。
  • async用来声明一个函数体,将其声明为异步方法,而await则是用在这个函数体的内部,用于等待一个异步操作的完成。
  • 异步方法内部可以出现一个或者多个await,一般不要是0个,这样就失去了异步方法的意义了。
  1. 异步方法中的线程切换:

(1).非UI线程中执行

        static void Main(string[] args)
        {
            MethodAsync1();
            Console.Read();
        }
        static async void MethodAsync1()
        {
            Console.WriteLine("当前主线程ID为:" + Thread.CurrentThread.ManagedThreadId );
            Console.WriteLine("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread);
            
            await Task.Run(() => {
                Console.WriteLine("第一个await当前线程ID为:" + Thread.CurrentThread.ManagedThreadId );
                Console.WriteLine("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread);
            });
            Console.WriteLine("第一个await结束后当前线程ID为:" + Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread);
            
            await Task.Run(() => {
                Console.WriteLine("第二个await当前线程ID为:" + Thread.CurrentThread.ManagedThreadId );
                Console.WriteLine("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread);
            });
            Console.WriteLine("第二个await结束后当前线程ID为:" + Thread.CurrentThread.ManagedThreadId );
            Console.WriteLine("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread);
        }

C#中的async和await的使用详解_第1张图片

该演示是在控制台应用程序中完成的,我们可以看到,主线程的ID为10,第一个await和紧接着之后的代码的线程ID为6和11,第二个await和紧接着之后的代码的线程ID为6,在非UI的线程中执行async异步方法,await等待的异步操作和之后接着要执行的代码,都是从线程池中获取了一个线程来执行代码,并且从线程池中获取的也不一定是同一个线程。

(2).UI线程中执行

那么,我们再来看一下,在UI线程中执行async的线程切换的规律:

        private void button1_Click(object sender, EventArgs e)
        {
            MethodAsync1();
        }
        string message;
        async void MethodAsync1()
        {
            message += ("当前主线程ID为:" + Thread.CurrentThread.ManagedThreadId + "\r\n");
            message += ("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread + "\r\n");
            await Task.Run(() => {
                message += ("第一个await当前线程ID为:" + Thread.CurrentThread.ManagedThreadId + "\r\n");
                message += ("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread + "\r\n");
            });
            message += ("第一个await结束后当前线程ID为:" + Thread.CurrentThread.ManagedThreadId + "\r\n");
            message += ("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread + "\r\n");
            await Task.Run(() => {
                message += ("第二个await当前线程ID为:" + Thread.CurrentThread.ManagedThreadId + "\r\n");
                message += ("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread + "\r\n");
            });
            message += ("第二个await结束后当前线程ID为:" + Thread.CurrentThread.ManagedThreadId + "\r\n");
            message += ("是否为线程池线程:" + Thread.CurrentThread.IsThreadPoolThread + "\r\n");
            this.richTextBox1.Text = message;
        }

C#中的async和await的使用详解_第2张图片

这个演示可以看到,在UI线程中使用async异步方法的时候,await后紧接着的代码,一直都会是在UI线程中执行。

(3).因此,在使用的时候需要注意这一点,在UI与非UI线程中执行async异步方法的时候,需要注意这两个细节,否则将会带来不必要的麻烦。

  1. 获取异步方法的返回值:
        static void Main(string[] args)
        {
            var a = MethodAsync1();
            Console.WriteLine(a.Result);
            Console.Read();
        }
        static async Task<int> MethodAsync1()
        {
            return await Task.Run(() =>
            {
                Thread.Sleep(1000);
                return 1;
            });
        }

C#中的async和await的使用详解_第3张图片

如代码所示,定义一个Task类型返回值的异步方法,先执行这个异步方法MethodAsync1(),然后获取该异步方法的返回值,在这个获取返回值的过程中,执行异步方法的原来的线程也会一直阻塞直到等到获取到返回值,毫无意外的程序在运行一秒钟以后,输出1。
这样显然是没有问题的,需要注意的是,上面我们说过,在UI线程与非UI线程中执行异步方法是有差异的,稍不注意,是可能带来死锁的现象的。
代码如下:

        private void button1_Click(object sender, EventArgs e)
        {
            var a = MethodAsync1();
            richTextBox1.Text = a.Result.ToString();
        }
        async Task<int> MethodAsync1()
        {
            await Task.Run(() =>
            {
                Thread.Sleep(1000);
            });
            return 1;
        }

C#中的async和await的使用详解_第4张图片

同样的是定义一个带Task类型返回值的异步方法,同样的也是先执行这个异步方法MethodAsync1(),然后获取该异步方法的返回值,但是,在这里,我们使用了WinForm程序来执行,该异步方法是在UI线程调用的,那么,现在就出现问题了,一旦点击button1按钮,程序便卡死了。

都是同样的用法,但是在这里就卡死了,什么情况呢?

上面说过了,在UI线程中调用的异步方法,在其await操作之后的一些代码,仍然实在UI线程中执行的,那么,首先,点击button1按钮按钮,异步方法执行await中的异步任务,UI线程就已经开始等这个结果了,在等的时候就把UI线程给阻塞了,而await任务执行完以后,return 1这个操作也需要在UI线程中执行,但是他已经被阻塞了,无法执行任何代码,就这样,我在等你返回值,你在等我释放UI线程来给你执行代码,谁也不相让,造成了程序的卡死。

当然了,实际上这样的操作本来就是不被允许的,UI线程不应该做任何的等待和阻塞处理,因为它需要实时地响应用户的界面请求。

你可能感兴趣的:(C#多线程编程)