Task-杂记

c# 异步编程变得越来越简单,这归功与async 和 await 。

异步编程相比大家随便找篇文章都能了解到他的含义和用法了,今天主要想弄清楚异步的执行顺序这些。先上一段代码

    static void Main(string[] args)
        {
            Console.WriteLine("主线程ID:", Thread.CurrentThread.ManagedThreadId);
            var name = SayHello();
            Console.WriteLine("执行结束");
            Console.Read();
        }

        static async Task<string> SayHello()
        {
            Console.WriteLine("Task.Run之前,线程Id: {0}", Thread.CurrentThread.ManagedThreadId);
            
            return await Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("'Task执行中,线程Id: {0}", Thread.CurrentThread.ManagedThreadId);
                return "Hello World";
            });
        }

 

执行结果:

很清楚的看到,再执行Task.Run之前,还是主线程ID的,然后执行之后能,就会开启另外的线程继续执行。

 

 

改变一下代码:

        static  void Main(string[] args)
        {
            Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId}");
            var name = SayHello2();
            Console.WriteLine(name.Result);
            Console.WriteLine("执行结束");
            Console.Read();
        }



        static   Task<string> SayHello2()
        {
            Console.WriteLine("Task.Run之前,线程Id: {0}", Thread.CurrentThread.ManagedThreadId);
            return  Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("'Task执行中,线程Id: {0}", Thread.CurrentThread.ManagedThreadId);
                return "Hello World";
            });

        }

执行结果为:

 

 当我们试图要访问Task.Result的时候,实际上就会等待Task执行完成。

 

下面探讨一下:ConfigureAwait。官方解释为:尝试将延续任务封送回原始上下文,则为 true;否则为 false

是的,当我们用await 关键字将 一个方法的同步代码分割开后,前后两段的代码的线程Id会一样吗? 带着这个思考我又改了一下程序:

        static async Task<string> SayHello()
        {
            Console.WriteLine("同步代码块1线程Id: {0}", Thread.CurrentThread.ManagedThreadId);

            var name = await Task.Run(() =>
            {
                //Thread.Sleep(1000);
                Console.WriteLine("'Task执行中,线程Id: {0}", Thread.CurrentThread.ManagedThreadId);
                return "Hello World";
            });

            Console.WriteLine("同步代码块2线程Id: {0}", Thread.CurrentThread.ManagedThreadId);

            var name2 = await Task.Run(() =>
            {
                //Thread.Sleep(1000);
                Console.WriteLine("'Task执行中,线程Id: {0}", Thread.CurrentThread.ManagedThreadId);
                return "Hello World";
            });


            Console.WriteLine("同步代码块3线程Id: {0}", Thread.CurrentThread.ManagedThreadId);

            return name;

        }

执行结果有点出乎我的意料,在分格的代码块执行使用了新的线程,而没有回到调用它的主线程执行,尽管我并没有使用ConfigureAwait(false)

 看了下资料,发现这个ConfigureaAwait的概念在UI和Asp.net 的上下文才关键。我们看一下一个WindowForm代码

      public static async Task WaitAsync()
        {
            // 这里 awati 会捕获当前上下文……
            await Task.Delay(TimeSpan.FromSeconds(1));// ……这里会试图用上面捕获的上下文继续执行
        }
        public static void Deadlock()
        {
            // 开始延迟
            Task task = WaitAsync();
            // 同步程序块,正在等待异步方法完成
            task.Wait();
        }

这样的代码执行会产生死锁,因为WaitAsyn里面先阻塞了UI线程,然后再执行完await task之后先要调回主线程执行,但是已经阻塞了,所以就造成了死锁。我们只需要用ConfigureaAwait改一下

    public static async Task WaitAsync()
        {

            await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);

        }
        public static void Deadlock()
        {

            Task task = WaitAsync();
            // 同步程序块,正在等待异步方法完成
            task.Wait();
        }

 

这样死锁就可以避免了。但是这里引申出一个原则:你代码使用了异步,最好一直使用异步,应该在调用结束时用wait等待他的执行结果,避免出现task.Wait.Task<T>.Result这样的代码。然后就是

在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行。
如果是 UI 上下文,并且有大量的 async 方法在 UI 上下文或者Asp.Net 上下文中恢复,就会引起性能上的问题。

posted @ 2018-12-23 15:24  UpOcean  阅读(183)  评论(0编辑  收藏  举报