C# async 方法怎么被正确的消费

using System;
using System.Threading.Tasks;

namespace netcore2._1_ {
    class Program {
        static void Main(string[] args) {
            for (int i = 0; i < 20; i++) {
                //M(123);
                M4(456, i);
                Console.WriteLine("done:{0}", i);
            }
            // Console.WriteLine(456);
            Console.ReadKey();
        }
        static async Task M(int x, int i) {
            await Task.Factory.StartNew(e => {
                Console.WriteLine(x);
                Task.Delay(1000);
            }, null);
            Console.WriteLine("async done:{0}", i);
        }
        static void M2(int x, int i) {
            Task.Factory.StartNew(e => {
                Console.WriteLine(x);
                Task.Delay(1000);
            }, null);
            Console.WriteLine("task done:{0}", i);
        }
        static void M3(int x) {
            Console.WriteLine(x);
        }
        /// <summary>
        /// 同步方法消费异步方法的接口,采用了状态共享穿越的闭包机制
        /// async方法就是一个线程方法,只是这个线程方法可以合并其它的
        /// await表达式的线程,产生相同可控的次序执行预期 这就是Task
        /// 链!
        /// 本方法通过void同步上下文去使用Task线程调用一个指定的async
        /// 方法,采用了,传递一个async匿名方法作用委托的方式进行,我们
        /// 可以看到,这里并没有任何编译警告,无缝的对接了同步与异步,是
        /// 最恰当的同步上下文消费异步async方法的封装!
        /// </summary>
        /// <param name="x"></param>
        /// <param name="i"></param>
        static void M4(int x, int i) {
            Task.Run(async () => { await M(x, i); });
        }
       // 同步方法调用async的接口写法,也是用到一个委托和原来用的回调事件有相同之处
        void M5(int x,int i) {
            Action<int,int> x2 = async (j,k) => await M(j,k);
            x2.Invoke(x,i);
        }

    }
}

关于async/await对, async就是异步方法标注一个异步方法,只是被标记了,标记为async的方法内容里必须有至少一个await表达式,不然要报错,直接异步的还是await表达式下面的线程,这种线程可能是Task
搞的,也可以是Threead搞的(我看有例子是await 了Thread的方法)
await的 表达式,是个关键,它是个真正多线程产生的地方,不管是一个Task代表的线程还是一个Thread代表的线程,因为net中代表线程的就这两个类

https://blog.csdn.net/u011033906/article/details/62885545?utm_source=blogxgwz2


绿色的是0片断,调用线程直接就会执行它,粉色是1片断,他被第一个await阻止了,但这时调用线程已经跑去执行其他的,可以说这个时候多核cpu同时在处理,调用线程上的语句,和此处await表达式线程上的语句可以认为他们是同步进行的,当await返回时,那么,意思是调用线程在其它地方的执行被中断了,然后跑来执行其后面的语句了 .... 一直这样,直到所有await全部这样处理完成,这样的确也是各种被await切分的语句片断 都处于 调用线程上,只不过让调用线程跳来跳去穿梭自如罢了,那些线程上下文切换 锁都不用考虑了,是不是这样?  
 下面测试来看看语句块的线程id
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1 {
    class Program {
        static void Main(string[] args) {
            var t = new Test1();
            t.Invoke();
            Console.ReadKey();
        }
    }
    class Test1 {
        string getCurrentThread => $"{Thread.CurrentThread.ManagedThreadId}";
        public void Invoke() {
            for (int i = 0; i < 2; i++) {
                //Test11();
                Test14();
            }
        }
        async void Test11() {
            Console.WriteLine(getCurrentThread);
            Console.WriteLine(nameof(Test11));
            Console.WriteLine(getCurrentThread);
        }
        async Task Test12() {
            Console.WriteLine($"{nameof(Test12)}-0:{getCurrentThread}");
            Console.WriteLine($"sentence1");
            await Task.Delay(1000);
            Console.WriteLine($"sentence2");
            Console.WriteLine($"{nameof(Test12)}-1:{getCurrentThread}");
        }
        async Task Test13(string content) {
            Console.WriteLine($"{nameof(Test13)}-0:{getCurrentThread}");
            await Task.Delay(1000);
            Console.WriteLine(content);
            Console.WriteLine($"{nameof(Test13)}-1:{getCurrentThread}");
        }
        async Task Test14() {
            Console.WriteLine($"{nameof(Test14)}-0:{getCurrentThread}");
            Console.WriteLine($"sentence1");
            await Test13($"Test14.Test13");
            Console.WriteLine($"sentence2");
            Console.WriteLine($"{nameof(Test14)}-1:{getCurrentThread}");

        }
    }
}


void Test19<T>(Func<T,Task> a, T t) {
Action<T> x = async x2 => await a(x2);
x.Invoke(t);
}
void Test20(int i) {
Test19<int>(Test17, i);
}


 

看看打印的线程id,似乎在不同的语句片断上有所不同

而改成非await的普通方法来测试似乎线程id并不会变见下图:

现在就已经可以证明了,被await分割的语句块从第一个await之后的代码块开始就已经与调用线程处于不同的线程了,并且如果你是一个async 1 : await *  的方法那么通常可能他会分一个额外的线程

如果循环多次进入异步就异步调用的次数是多次的话,那么,分派去处理的线程个数就会增加,最后可能会根本系统的情况来确定一个合理的线程数量组成一个线程池。

所以网上有说 async不创建线程的说法可能是不对的!

再来看看 两个测试结果

我们可以看到,当Task.Delay(0)的时候如果直接跑 似乎await并没有分出线程来了,可以说这是一个特殊的 Task对象,如果>0的那结果当然会是 非1(当前线程id)的,在使用Task.Run包裹一下Task.Delay(0) 时,我们看到 线程id不同了

再来看一个结果可以更清楚的说明问题所在:

这是循环执行2次的情况,可以发现,在同一个 current idx:i 下 sentence x 和对应的 sentence *x 的 线程id 号是一致的

得出的一些猜测:

1,一个await 表达式,并不等于分派一个线程,线程总数不会随着await表达式的增加而增加,

2, await和Task是一 一对应的,await表达式就是一个返回 Task的结果,所以有人说Task是占位符

3,语句片断按顺序执行

4,第一个语句片断是和调用线程相同的,后续的其它片断,可能处于一个或多个不同的线程之中

5,await之间的语句片断的线程与其前面的await表达式中Task 所属于的线程是一致的

6,async方法有污染扩散的倾向,但只要加上同步消费async接口方法的就可以解决这个问题

7,调用上下文的async消费接口方法,是使用一个对委托变量加上async标注来实现的,比如 Action<object> x= async x=> await Task_Expersion; x.Invoke(xx);

8,更简单的消费方式是 Task.Wait();方法,让异步变同步

原文地址:https://www.cnblogs.com/ProjectDD/p/9820175.html