首页
关于
Search
1
我们的邀请函
5 阅读
2
结婚物品事项清单
3 阅读
3
杭州 露营 🏕
3 阅读
4
PVE 虚拟机转 ESXI
3 阅读
5
轻量级.Net Core服务注册工具CodeDi发布啦
2 阅读
默认分类
小白入门
开源项目
深度技术
技术弄潮
数据库
事故总结
万物互联
旅行
户外
notes
生活瞬间
小工具
IT
PMP
登录
Search
标签搜索
欧洲
生活瞬间
航拍
摄影
旅游
自驾
telegram
tdl
申根
防盗
装修
pve
esxi
PMP
Jason Liu
累计撰写
26
篇文章
累计收到
1
条评论
首页
栏目
默认分类
小白入门
开源项目
深度技术
技术弄潮
数据库
事故总结
万物互联
旅行
户外
notes
生活瞬间
小工具
IT
PMP
页面
关于
搜索到
3
篇与
的结果
2022-05-25
不一样的角度一窥多线程
不一样的角度一窥多线程最近在性能调试时,发现了一个有趣的现象,我把代码简化后如下. class Program { static void Main(string[] args) { Console.WriteLine("Start..."); DoSomething(); Console.WriteLine("Ending..."); Console.ReadLine(); } static void DoSomething() { var sum=""; for (int i = 2; i < int.MaxValue; i++) { sum += "s"; } Console.WriteLine(sum.Length); } } 可以看到,非常简单的一段代码,当我用Windows的性能监测工具来监测每个处理器的使用率时,发现了一个有趣的现象.我电脑是四核的I7处理器,执行以上代码后,却只有处理器2一直处理一个比较高的占用率,而其他的三个则处于一个"摸鱼混日子"的状态,处理器1则更过分,你是睡着了吗?同一台电脑上的处理器,难道大家不是有福同享,有难同当的吗? 为什么其他几个处理器就忍心看着处理器2水深火热呢?然后,我就和这个问题死磕上了,恶补了一些操作系统与多线程的知识,现在把一写知识点串起来,分享给大家.分级保护域电脑操作系统提供不同的资源访问级别。在计算机体系结构中,Rings是由两个或更多的特权态组成。在一些硬件或者微代码级别上提供不同特权态模式的CPU架构上,保护环通常都是硬件强制的。Rings是从最高特权级(通常被叫作0级)到最低特权级(通常对应最大的数字)排列的。在大多数操作系统中,Ring 0拥有最高特权,并且可以和最多的硬件直接交互(比如CPU,内存)。在Windows中, User Space,也就是我们自己安装的那些应用程序处理Ring 3,而系统内核就在Ring 0.对于这个问题,举个例子,大家就好理解了.钱不是万能的,但没钱是万万不能的,所以钱是一个家庭的重中之重,家里老婆呢为了这个家的长治久安,掌握家里的财政大权,把家里的小金库守得死死的,但这就意味着我没钱花了吗?当然不是,和老婆大人用正当理由申请不就完事了?😂申请通过之后,老婆大人是允许我直接伸手去家里小金库拿钱吗? 那当然不是,如果我一抓一大把就危险了,所以还得经过她的手从小金库拿钱给我.这个现象,我觉得也是一种分级保护域,所以呢,也一直对老婆大人的这种万恶行径表示理解.操作系统也是这样,CPU,内存这些硬件是电脑安全的根本,所以不能给第三方软件操作权限,想操作硬件,就通过由Ring 0中内核(Kernel)暴露的严格Api进行.用户级线程与内核级线程线程主要有以下两种实现方式- 用户级线程 -用户托管线程。 内核级线程 -作用在内核(操作系统核心)上的操作系统管理的线程。 在上图中,User Space就可以理解为我上个章节中的Ring 3,而Kernel Space就是Ring 0, 在Ring 0中,是可以直接操作CPU,内存等硬件的,而Ring 3不行.以下是用户级线程与内核级线程的对比. 用户级线程 内核级线程 用户线程由用户实现。 内核线程由OS实现。 操作系统无法识别用户级线程。 内核线程被操作系统识别。 用户线程的实现很容易。 内核线程的实现很复杂。 上下文切换时间更少。 上下文切换时间更长。 上下文切换不需要硬件支持。 需要硬件支持。 如果一个用户级别的线程执行阻止操作,则整个过程将被阻止。 如果一个内核线程执行阻止操作,则另一线程可以继续执行。 无法直接发挥多核处理器的优势 可以享受多处理起带来的好处 其中,非常重要的一点,用户级线程无法直接发挥多核处理器的优势,难道我们编写出来的代码只能在一个处理器上运行了吗?这就要讲讲用户级线程模型.用户级线程模型通常,内核级线程可以使用三个模型之一来执行用户级线程。 Many-to-one One-to-one Many-to-many 所有模型都将用户级线程映射到内核级线程,一个内核线程就像一个处理器,它是系统编排任务的基本单位。Many-to-one多对一模型将许多用户级线程映射到一个内核级线程。线程管理是通过线程库在用户空间中完成的。当线程进行阻塞的系统调用时,整个过程将被阻塞。一次只能有一个线程访问内核,因此多个线程无法在多处理器上并行运行。如果用户级线程库是以操作系统不支持的方式实现的,则内核线程将使用多对一关系模型。内核对用户级线程不可见,在它眼里只有内核线程,而在内核线程的眼里,一个进程无非就是一个偶尔被被它翻牌的黑盒子,进程负责用户线程的调度与执行.One-to-one在这种模型下用户级线程与内核级线程之间存在一对一的关系。该模型比多对一模型并发性好,当一个线程进行阻塞系统调用时,它还允许另一个线程运行,所以它支持多个线程以在处理器上并行执行。该模型的缺点是创建用户线程需要相应的内核线程,而创建内核线程开销是很大的.Many-to-many在多对多模型中,m个内核线程处理n个用户线程,其中m < n. 该模型并发性最好,并且不用创建过多的内核线程,涉及到的线程切换同步的开销也更小.真相浮出水面.Net的代码作为托管代码在“托管线程”上执行,而托管线程是在CLR虚拟机上执行的虚拟线程,也是属于用户级线程.正如JIT编译器将“虚拟” IL指令映射到在物理计算机上执行的本机指令一样,CLR的线程基础结构也将“虚拟”托管线程映射到操作系统提供的内核线程。说到这里,我们也差不多有了前面我说的那个现象的答案了,并非其他处理器不想与那个水深火热的处理器有难同享,而是我没有使用多线程,所以执行的程序只有一个主线程,也就是说用户线程数为1.只能是one to one 模型,所以只有一个处理器能参与工作.既然知道了里面的原理,那我们就对前文中的程序进行改造,创建四个线程来执行任务,会不会所有处理器都忙起来呢? class Program { static void Main(string[] args) { Console.WriteLine("Start..."); for (var i = 0; i < 4; i++) { var td=new Thread(DoSomething); td.Start(); } Console.WriteLine("Ending..."); Console.ReadLine(); } static void DoSomething() { var sum=""; for (int i = 2; i < int.MaxValue; i++) { sum += "s"; } Console.WriteLine(sum.Length); } } 可以看到,这次大家的步伐都做到了惊人的一致,四个处理器都被调用起来,加上主线程,这里至少有五个用户线程,所以这里应该是many to many的模型了.谢谢观赏!参考资料:https://en.wikipedia.org/wiki/Protection_ringhttps://stackoverflow.com/questions/15093510/whats-c-sharp-threading-typehttps://github.com/dotnet/coreclr/blob/master/Documentation/botr/threading.md#clr-threading-overview
2022年05月25日
1 阅读
0 评论
0 点赞
2022-05-25
C# 9.0新特性
CandidateFeaturesForCSharp9看到标题,是不是认为我把标题写错了?是的,C# 8.0还未正式发布,在官网它的最新版本还是Preview 5,通往C#9的漫长道路却已经开始.前写天收到了活跃在C#一线的BASSAM ALUGILI给我分享C# 9.0新特性,我在他文章的基础上进行翻译,希望能对大家有所帮助.这是世界上第一篇关于C#9候选功能的文章。阅读完本文后,你将会为未来可能遇到的C# 9.0新特性做好更充分的准备。这篇文章基于, C# 9.0候选新特性 原生大小的数字类型这次引入一组新类型(nint,nuint,nfloat等)'n’表示native(原生),该特性允许声明一个32位或64位的数据类型,这取决于操作系统的平台类型。nint nativeInt = 55; take 4 bytes when I compile in 32 Bit host. nint nativeInt = 55; take 8 bytes when I compile in 64 Bit host with x64 compilation settings. xamarin中已存在类似的概念, xamarin原生类型 Records and Pattern-based With-Expression这个功能我等待了很长时间,Records是一种轻量级的不可变类型,它可以是方法,属性,运算符等,它允许我们进行结构的比较, 此外,默认情况下,Records属性是只读的。Records可以是值类型或引用类型。Examplepublic class Point3D(double X, double Y, double Z); public class Demo { public void CreatePoint() { var p = new Point3D(1.0, 1.0, 1.0); } } 这些代码会被编译器转化如下形式.public class Point3D { private readonly double <X>k__BackingField; private readonly double <Y>k__BackingField; private readonly double <Z>k__BackingField; public double X {get {return <X>k__BackingField;}} public double Y{get{return <Y>k__BackingField;}} public double Z{get{return <Z>k__BackingField;}} public Point3D(double X, double Y, double Z) { <X>k__BackingField = X; <Y>k__BackingField = Y; <Z>k__BackingField = Z; } public bool Equals(Point3D value) { return X == value.X && Y == value.Y && Z == value.Z; } public override bool Equals(object value) { Point3D value2; return (value2 = (value as Point3D)) != null && Equals(value2); } public override int GetHashCode() { return ((1717635750 * -1521134295 + EqualityComparer<double>.Default.GetHashCode(X)) * -1521134295 + EqualityComparer<double>.Default.GetHashCode(Y)) * -1521134295 + EqualityComparer<double>.Default.GetHashCode(Z); } } Using Records: public class Demo { public void CreatePoint() { Point3D point3D = new Point3D(1.0, 1.0, 1.0); } } Records迎合了基于表达式形式编程的特性,使得我们可以这样使用它.var newPoint3D = Point3D.With(x: 42); 这样我们创建的新Point(new Point3D)就像现有的一个(point3D)一样并把X的值更改为42。这个特性于基于pattern matching也非常有效,我会在我的下一篇文章中介绍这一点.那么我们为什么要使用Records而不是用结构体呢?为了回答这些问题,我引用了了Reddit的一句话:“结构体是你必须要有一些约定来实现的东西。你不必手动地去让它只读,你也不用去实现他们的比较逻辑,但如果你不这样做,那你就失去了使用结构体的意义,编译器不会强制执行这些约束"。Records类型由是编译器实现,这意味着您必须满足所有这些条件并且不能错误, 因此,它们不仅可以减少重复代码,还可以消除一大堆潜在的错误。此外,这个功能在F#中存在了十多年,其他语言如(Scala,Kotlin)也有类似的概念。F#type Greeter(name: string) = member this.SayHi() = printfn "Hi, %s" name Scalaclass Greeter(name: String) { def SayHi() = println("Hi, " + name) } Kotlinclass Greeter(val name: String) { fun sayhi() { println("Hi, ${name}"); } } 在没有Records之前,我们要实现类似的功能,C#代码要这么写C#public class Greeter { private readonly string _name; public Greeter(string name) { _name = name; } public void Greet() { Console.WriteLine($ "Hello, {_name}"); } } 有了Records之后,我们可以将C#代码大大地减少了,ublic class Greeter(name: string) { public void Greet() { Console.WriteLine($ "Hello, {_name}"); } } Less code! = I love it!Type Classes此功能的灵感来自Haskell,它是我最喜欢的功能之一。正如我两年前在我文章中所说,C#将实现更多的函数式编(FP)程概念,Type Classes就是FP概念之一。在函数式编程中,Type Classes允许您在类型上添加一组操作,但不实现它。由于实现是在其他地方完成的,这是一种多态,它比面向对象编程语言中的class更灵活。Type Classes和C#接口具有相似的用途,但它们的工作方式有所不同,在某些情况下,由于处理固定类型而不是继承层次结构,因此Type Classes更易于使用。此这特性最初与“extending everything”功能一起引入,您可以将它们组合在一起,如Mads Torgersen给出的例子所示。我引用了官方提案中的一些结论:“一般来说,”shape“(shape是Type Classes的一个新的关键字)声明非常类似于接口声明,除了以下情况, 它可以定义任何类型的成员(包括静态成员) 可以通过扩展实现 只能在指定的地方当作一种类型使用(作用域)“ Haskell中 Type Classes示例。class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool “Eq”是类名,而==,/ =是类中的操作。类型“a”是类“Eq”的实例。如果我们将上述例子用C#接口实现将会是这样.interface Num<A> { A Add(A a, A b); A Mult(A a, A b); A Neg(A a); } struct NumInt : Num<int> { public int Add(int a, int b) => a + b; public int Mult(int a, int b) => a * b; public int Neg(int a) => -a; } 如果我们用Type Classes实现C# 功能会是这样shape Num<A> { A Add(A a, A b); A Mult(A a, A b); A Neg(A a); } instance NumInt : Num<int> { int Add(int a, int b) => a + b; int Mult(int a, int b) => a * b; int Neg(int a) => -a; } 通过上面例子,可以看到接口和shape的语法类似 ,那我们再来看看Mads Torgersen给出的例子Note:shape不是一种类型。相反,shape的主要目的是用作通用约束,限制类型参数以具有正确的形状,同时允许通用声明的主体使用该形状,原始来源public shape SGroup<T> { static T operator +(T t1, T t2); static T Zero {get;} } 这个声明说如果一个类型在T上实现了一个+运算符并且具有0静态属性,那么它可以是一个SGroup 。给int添加静态成员Zeropublic extension IntGroup of int: SGroup<int> { public static int Zero => 0; } 定义一个AddAll方法public static AddAll<T>(T[] ts) where T: SGroup<T> // shape used as constraint { var result = T.Zero; // Making use of the shape's Zero property foreach (var t in ts) { result += t; } // Making use of the shape's + operator return result; } 让我们用一些整数调用AddAll方法,int[] numbers = { 5, 1, 9, 2, 3, 10, 8, 4, 7, 6 }; WriteLine(AddAll(numbers)); // infers T = int 这就是Type class 的妙处,慢慢消化感受一下??Dictionary Literals引入更简单的语法来创建初始化的Dictionary <TKey,TValue>对象,而无需指定Dictionary类型名称或类型参数。使用用于数组类型推断的现有规则推断字典的类型参数。// C# 1..8 var x = new Dictionary <string,int> () { { "foo", 4 }, { "bar", 5 }}; // C# 9 var x = ["foo":4, "bar": 5]; 该特性使C#中的字典工作更简单,并删除冗余代码。此外,值得一提的是,在F#和Swift等其他编程语言中也使用了类似的字典语法。Params Span 允许params语法使用Span 这个帮助来实现没有任何堆分配的params参数传递。此功能可以使params方法的使用更加高效。新的语法如下,void Foo(params Span<int> values); struct允许使用无参构造函数到目前为止,在C#中不允许在结构体声明中使用无参构造函数,在C#9中,将删除此限制。StackOverflow示例public struct Rational { private long numerator; private long denominator; public Rational(long num, long denom) { /* Todo: Find GCD etc. */ } public Rational(long num) { numerator = num; denominator = 1; } public Rational() // This is not allowed { numerator = 0; denominator = 1; } } 链接到StackOverflow示例其实CLR已经允许值类型数据具有无参构造函数,只是C# 对这个功能进行了限制,在C# 9.0中可能会消除这种限制.固定大小的缓冲区这些提供了一种通用且安全的机制,用于向C#语言声明固定大小的缓冲区。目前,用户可以在不安全的环境中创建固定大小的缓冲区。但是,这需要用户处理指针,手动执行边界检查,并且只支持一组有限的类型(bool,byte,char,short,int,long,sbyte,ushort,uint,ulong,float和double)。该特性引入后将使固定大小的缓冲区变得安全安全,如下例所示。可以通过以下方式声明一个安全的固定大小的缓冲区,public fixed DXGI_RGB GammaCurve[1025]; 该声明将由编译器转换为内部表示,类似于以下内容,[FixedBuffer(typeof(DXGI_RGB), 1024)] public ConsoleApp1.<Buffer>e__FixedBuffer_1024<DXGI_RGB> GammaCurve; // Pack = 0 is the default packing and should result in indexable layout. [CompilerGenerated, UnsafeValueType, StructLayout(LayoutKind.Sequential, Pack = 0)] struct <Buffer>e__FixedBuffer_1024<T> { private T _e0; private T _e1; // _e2 ... _e1023 private T _e1024; public ref T this[int index] => ref (uint)index <= 1024u ? ref RefAdd<T>(ref _e0, index): throw new IndexOutOfRange(); } Uft8字符串文字它是关于定义一种新的字符串类型UTF8String,它将是,System.UTF8String myUTF8string ="Test String"; Base(T)此功能用于解决默认接口方法中的覆盖冲突问题:interface I1 { void M(int) { } } interface I2 { void M(short) { } } interface I3 { override void I1.M(int) { } } interface I4 : I3 { void M2() { base(I3).M(0) // Which M should be used here? What does this do? } } 更多信息, https://github.com/dotnet/csharplang/issues/2337 https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-02-27.md 摘要您已经阅读了第一个C#9候选特性。正如您所看到的,许多新功能受到其他编程语言或编程范例的启发,而不是自我创新,这些特性大部分在在社区中得到了广泛认可,所以引入C# 后应该也会给大家带来不错的体验.原文 : https://www.c-sharpcorner.com/article/candidate-features-for-c-sharp-9/
2022年05月25日
1 阅读
0 评论
0 点赞
2022-05-25
聊一聊C# 8.0中的await foreach
AsyncStreamsInCShaper8.0很开心今天能与大家一起聊聊C# 8.0中的新特性-Async Streams,一般人通常看到这个词表情是这样.简单说,其实就是C# 8.0中支持await foreach.或者说,C# 8.0中支持异步返回枚举类型async Task<IEnumerable<T>>.好吧,还不懂?Good,这篇文章就是为你写的,看完这篇文章,你就能明白它的神奇之处了.为什么写这篇文章Async Streams这个功能已经发布很久了,在去年的Build 2018 The future of C#就有演示,最近VS 2019发布,在该版本的Release Notes中,我再次看到了这个新特性,因为对异步编程不太熟悉,所以借着这个机会,学习新特性的同时,把异步编程重温一遍.本文内容,参考了Bassam Alugili在InfoQ中发表的Async Streams in C# 8,撰写本博客前我已联系上该作者并得到他支持.Async / AwaitC# 5 引入了 Async/Await,用以提高用户界面响应能力和对 Web 资源的访问能力。换句话说,异步方法用于执行不阻塞线程并返回一个标量结果的异步操作。微软多次尝试简化异步操作,因为 Async/Await 模式易于理解,所以在开发人员当中获得了良好的认可。详见The Task asynchronous programming model in C#常规示例要了解问什么需要Async Streams,我们先来看看这样的一个示例,求出5以内的整数的和.static int SumFromOneToCount(int count) { ConsoleExt.WriteLine("SumFromOneToCount called!"); var sum = 0; for (var i = 0; i <= count; i++) { sum = sum + i; } return sum; } 调用方法.static void Main(string[] args) { const int count = 5; ConsoleExt.WriteLine($"Starting the application with count: {count}!"); ConsoleExt.WriteLine("Classic sum starting."); ConsoleExt.WriteLine($"Classic sum result: {SumFromOneToCount(count)}"); ConsoleExt.WriteLine("Classic sum completed."); ConsoleExt.WriteLine("################################################"); } 输出结果.可以看到,整个过程就一个线程Id为1的线程自上而下执行,这是最基础的做法.Yield Return接下来,我们使用yield运算符使得这个方法编程延迟加载,如下所示.static IEnumerable<int> SumFromOneToCountYield(int count) { ConsoleExt.WriteLine("SumFromOneToCountYield called!"); var sum = 0; for (var i = 0; i <= count; i++) { sum = sum + i; yield return sum; } } 主函数static void Main(string[] args) { const int count = 5; ConsoleExt.WriteLine("Sum with yield starting."); foreach (var i in SumFromOneToCountYield(count)) { ConsoleExt.WriteLine($"Yield sum: {i}"); } ConsoleExt.WriteLine("Sum with yield completed."); ConsoleExt.WriteLine("################################################"); ConsoleExt.WriteLine(Environment.NewLine); } 运行结果如下.正如你在输出窗口中看到的那样,结果被分成几个部分返回,而不是作为一个值返回。以上显示的累积结果被称为惰性枚举。但是,仍然存在一个问题,即 sum 方法阻塞了代码的执行。如果你查看线程ID,可以看到所有东西都在主线程1中运行,这显然不完美,继续改造.Async Return我们试着将async用于SumFromOneToCount方法(没有yield关键字).static async Task<int> SumFromOneToCountAsync(int count) { ConsoleExt.WriteLine("SumFromOneToCountAsync called!"); var result = await Task.Run(() => { var sum = 0; for (var i = 0; i <= count; i++) { sum = sum + i; } return sum; }); return result; } 主函数.static async Task Main(string[] args) { const int count = 5; ConsoleExt.WriteLine("async example starting."); // Sum runs asynchronously! Not enough. We need sum to be async with lazy behavior. var result = await SumFromOneToCountAsync(count); ConsoleExt.WriteLine("async Result: " + result); ConsoleExt.WriteLine("async completed."); ConsoleExt.WriteLine("################################################"); ConsoleExt.WriteLine(Environment.NewLine); } 运行结果.我们可以看到计算过程是在另一个线程中运行,但结果仍然是作为一个值返回!任然不完美.如果我们想把惰性枚举(yield return)与异步方法结合起来,即返回Task<IEnumerable,这怎么实现呢?Task<IEnumerable>我们根据假设把代码改造一遍,使用Task<IEnumerable<T>>来进行计算.可以看到,直接出现错误.IAsyncEnumerable其实,在C# 8.0中Task<IEnumerable>这种组合称为IAsyncEnumerable。这个新功能为我们提供了一种很好的技术来解决拉异步延迟加载的问题,例如从网站下载数据或从文件或数据库中读取记录,与 IEnumerable 和 IEnumerator 类似,Async Streams 提供了两个新接口 IAsyncEnumerable 和 IAsyncEnumerator,定义如下:public interface IAsyncEnumerable<out T> { IAsyncEnumerator<T> GetAsyncEnumerator(); } public interface IAsyncEnumerator<out T> : IAsyncDisposable { Task<bool> MoveNextAsync(); T Current { get; } } // Async Streams Feature 可以被异步销毁 public interface IAsyncDisposable { Task DiskposeAsync(); } AsyncStream下面,我们就来见识一下AsyncStrema的威力,我们使用IAsyncEnumerable来对函数进行改造,如下.static async Task ConsumeAsyncSumSeqeunc(IAsyncEnumerable<int> sequence) { ConsoleExt.WriteLineAsync("ConsumeAsyncSumSeqeunc Called"); await foreach (var value in sequence) { ConsoleExt.WriteLineAsync($"Consuming the value: {value}"); // simulate some delay! await Task.Delay(TimeSpan.FromSeconds(1)); }; } private static async IAsyncEnumerable<int> ProduceAsyncSumSeqeunc(int count) { ConsoleExt.WriteLineAsync("ProduceAsyncSumSeqeunc Called"); var sum = 0; for (var i = 0; i <= count; i++) { sum = sum + i; // simulate some delay! await Task.Delay(TimeSpan.FromSeconds(0.5)); yield return sum; } } 主函数. static async Task Main(string[] args) { const int count = 5; ConsoleExt.WriteLine("Starting Async Streams Demo!"); // Start a new task. Used to produce async sequence of data! IAsyncEnumerable<int> pullBasedAsyncSequence = ProduceAsyncSumSeqeunc(count); // Start another task; Used to consume the async data sequence! var consumingTask = Task.Run(() => ConsumeAsyncSumSeqeunc(pullBasedAsyncSequence)); await Task.Delay(TimeSpan.FromSeconds(3)); ConsoleExt.WriteLineAsync("X#X#X#X#X#X#X#X#X#X# Doing some other work X#X#X#X#X#X#X#X#X#X#"); // Just for demo! Wait until the task is finished! await consumingTask; ConsoleExt.WriteLineAsync("Async Streams Demo Done!"); } 如果一切顺利,那么就能看到这样的运行结果了.最后,看到这就是我们想要的结果,在枚举的基础上,进行了异步迭代.可以看到,整个计算过程并没有造成主线程的阻塞,其中,值得重点关注的是红色方框区域的线程5!线程5!线程5!线程5在请求下一个结果后,并没有等待结果返回,而是去了Main()函数中做了别的事情,等待请求的结果返回后,线程5又接着执行foreach中任务.Client/Server的异步拉取如果还没有理解Async Streams的好处,那么我借助客户端 / 服务器端架构是演示这一功能优势的绝佳方法。同步调用客户端向服务器端发送请求,客户端必须等待(客户端被阻塞),直到服务器端做出响应.示例中Yield Return就是以这种方式执行的,所以整个过程只有一个线程即线程1在处理.异步调用客户端发出数据块请求,然后继续执行其他操作。一旦数据块到达,客户端就处理接收到的数据块并询问下一个数据块,依此类推,直到达到最后一个数据块为止。这正是 Async Streams 想法的来源。最后一个示例就是以这种方式执行的,线程5询问下一个数据后并没有等待结果返回,而是去做了Main()函数中的别的事情,数据到达后,线程5又继续处理foreach中的任务.Tips如果你使用的是.net core 2.2及以下版本,会遇到这样的报错.需要安装.net core 3.0 preview的SDK(截至至博客撰写日期4月9日,.net core SDK最新版本为3.0.100-preview3-010431),安装好SDK后,如果你是VS 2019正式版,可能无法选择.net core 3.0,vs 2019 正式版默认情况下没有开启对预览版.net core 3.0 的支持.根据网友补充,需要在VS 2019正式版本中需要开启使用 .Net core SDK 预览版,才能创建3.0的项目.工具 > 选项 > 项目和解决方案 > .Net Core > 使用 .Net core SDK 预览版总结我们已经讨论过 Async Streams,它是一种出色的异步拉取技术,可用于进行生成多个值的异步计算。Async Streams 背后的编程概念是异步拉取模型。我们请求获取序列的下一个元素,并最终得到答复。Async Streams 提供了一种处理异步数据源的绝佳方法,希望对大家能够有所帮助。文章中涉及的所有代码已保存在我的GitHub中,请尽情享用!https://github.com/liuzhenyulive/AsyncStreamsInCShaper8.0致谢之前一直感觉国外的大师级开发者遥不可及甚至高高在上,在遇到Bassam Alugili之后,我才真正感受到技术交流没有高低贵贱,正如他对我说的 The most important thing in this world is sharing the knowledge!Thank you,I will keep going!!参考文献: Async Streams in C# 8 https://www.infoq.com/articles/Async-Streams
2022年05月25日
1 阅读
0 评论
0 点赞