.NET Core 中的高并发程序编写

摘要: 高并发程序编写 - 多线程 vs. 多段程编码并行处理程序编写是一个普遍的专业术语,大家应当根据观查多线程方式和具体的多段程中间的差别进行讨论。 虽然 .NET Core 应用了每日任务来...

高并发程序编写 - 多线程 vs. 多段程编码

并行处理程序编写是一个普遍的专业术语,大家应当根据观查多线程方式和具体的多段程中间的差别进行讨论。 虽然 .NET Core 应用了每日任务来表述一样的定义,一个重要的差别是內部解决的不一样。 启用进程在做别的事儿时,多线程方式在后台管理运作。这寓意着这种方式是 I/O 聚集型的,即她们大部分分时图间用以键入和輸出实际操作,比如文档或互联网浏览。 要是有将会,应用多线程 I/O 方式替代同歩实际操作很更有意义。同样的時间,启用进程能够在解决桌面上运用程序中的客户互动或解决网络服务器运用程序中的同时解决别的恳求,而不但仅是等候实际操作进行。

测算聚集型的方式规定 CPU 周期时间工作中,而且只有运作在她们专用型的后台管理进程中。CPU 的关键数限定了并行处理运作时的能用进程总数。实际操作系统软件承担在剩下的进程中间转换,使她们有机化学会实行编码。 这种方式依然被高并发地实行,却无须被并行处理地实行。虽然这寓意着方式并不是同时实行,却能够在别的方式中止的情况下实行。

并行处理 vs 高并发

文中将在最终一段中关键详细介绍 在 .NET Core中有进程高并发程序编写。

每日任务并行处理库

.NET Framework 4 引进了每日任务并行处理库 (TPL) 做为撰写高并发编码的优选 API。.NET Core选用同样的程序编写方式。 要在后台管理运作一段编码,必须将其包裝成一个 每日任务:

var backgroundTask = Task.Run(() = DoComplexCalculation(42));// do other workvar result = backgroundTask.Result;

当必须回到結果时,Task.Run 方式接受一个 涵数 (Func) ;当不用回到結果时,方式 Task.Run 接受一个 姿势 (Action) 。自然,全部的状况下都可以令其用 lambda 表述式,如同我上边事例中启用带一个主要参数的长期方式。 进程池中的某一进程可能解决每日任务。.NET Core 的运作时包括一个默认设置生产调度程序,应用进程池来解决序列并实行每日任务。您能够根据派生 TaskScheduler 类完成自身的生产调度优化算法,替代默认设置的,但这超出文中的探讨范畴。 如同大家以前所闻,我应用 Result 特性来合拼被启用的后台管理进程。针对不用回到結果的进程,我能启用 Wait() 来替代。这二种方法都将被阻塞到后台管理每日任务进行。 以便防止阻塞启用进程 ( 当在ASP.NET Core运用程序中) ,可使用 await 重要字:

var backgroundTask = Task.Run(() = DoComplexCalculation(42));// do other workvar result = await backgroundTask;

那样被启用的进程将被释放出来便于解决别的传到恳求。一旦每日任务进行,一个能用的工作中进程可能再次解决恳求。自然,操纵器姿势方式务必是多线程的:

public async Task iactionresult Index() { // method body }

解决出现异常

将2个进程合拼在一起的情况下,每日任务抛出去的一切出现异常将被传送到启用进程中:

假如应用 Result 或 Wait() ,他们将挨打包到 AggregateException 中。具体的出现异常将被抛出去共存储在其 InnerException 特性中。

假如您应用 await,原先的出现异常将不容易挨打包。

在这里二种状况下,启用堆栈的信息内容将维持不会改变。

撤销每日任务

因为每日任务是能够长期运作的,因此你可以能要想有一个能够提早撤销每日任务的选择项。完成这一选择项,必须在每日任务建立的情况下传到撤销的令牌 (token),以后再应用令牌开启撤销每日任务:

var tokenSource = new CancellationTokenSource();var cancellableTask = Task.Run(() = {for (int i = 0; i 100; i++){if (tokenSource.Token.IsCancellationRequested){// clean up before exitingtokenSource.Token.ThrowIfCancellationRequested();}// do long-running processing}return 42;}, tokenSource.Token);// cancel the tasktokenSource.Cancel();try{await cancellableTask;}catch (OperationCanceledException e){// handle the exception} 

具体上,以便提早撤销每日任务,你必须查验每日任务中的撤销令牌,并在必须撤销的情况下做出反映:在实行必需的清除实际操作后,启用 ThrowIfCancellationRequested() 撤出每日任务。这一方式可能抛出去 OperationCanceledException,便于在启用进程中实行相对的解决。

融洽多个任务

假如你必须运作好几个后台管理每日任务,这儿一些方式能够协助到你。 要同时运作好几个每日任务,只需持续起动他们并搜集他们的引入,比如在数字能量数组中:

var backgroundTasks = new []{Task.Run(() = DoComplexCalculation(1)),Task.Run(() = DoComplexCalculation(2)),Task.Run(() = DoComplexCalculation(3))};

如今你可以令其用 Task 类的静态数据方式,等候她们被多线程或是同歩实行结束。

// wait synchronouslyTask.WaitAny(backgroundTasks);Task.WaitAll(backgroundTasks);// wait asynchronouslyawait Task.WhenAny(backgroundTasks);await Task.WhenAll(backgroundTasks);

具体上,这2个方式最后都是回到全部每日任务的本身,能够像一切别的每日任务一样再度实际操作。以便获得相匹配每日任务的結果,你可以以查验该每日任务的 Result 特性。 解决多个任务的出现异常有点儿繁杂。方式 WaitAll 和 WhenAll 无论哪一个每日任务被搜集到出现异常时都是抛出去出现异常。但是,针对 WaitAll ,可能搜集全部的出现异常到相匹配的 InnerExceptions 特性;针对 WhenAll ,总是抛出去第一个出现异常。以便确定哪一个每日任务抛出去了哪一个出现异常,您必须独立查验每一个每日任务的 Status 和 Exception 特性。 在应用 WaitAny 和 WhenAny 时务必充足当心。她们会直到第一个每日任务进行 (取得成功或不成功),即便某一每日任务出現出现异常时都不会抛出去一切出现异常。她们总是回到完成每日任务的数据库索引或是各自回到完成的每日任务。你务必直到每日任务进行或浏览其 result 特性时捕捉出现异常,比如:

假如你要持续运作好几个每日任务,替代高并发每日任务,可使用持续 (continuations)的方法:

ContinueWith() 方式容许你将好几个每日任务一个然后一个实行。这一持续的每日任务将获得到前边每日任务的結果或情况的引入。 你依然能够提升标准分辨是不是实行持续每日任务,比如仅有在前边每日任务取得成功实行或是抛出去出现异常时。比照持续等候好几个每日任务,提升了灵便性。 自然,您能够将持续每日任务与以前探讨的全部作用紧密结合:出现异常解决、撤销和并行处理运作每日任务。这就会有了非常大的演出室内空间,以不一样的方法开展组成:

var multipleTasks = new[]{Task.Run(() = DoComplexCalculation(1)),Task.Run(() = DoComplexCalculation(2)),Task.Run(() = DoComplexCalculation(3))};binedTask = Task.WhenAll(multipleTasks);essfulContinuation = combinedTask.ContinueWith(task = CombineResults(task.Result), TaskContinuationOptions.OnlyOnRanToCompletion);var failedContinuation = combinedTask.ContinueWith(task = HandleError(task.Exception), TaskContinuationOptions.NotOnRanToCompletion);await Task.essfulContinuation, failedContinuation);

每日任务同歩

假如每日任务是彻底单独的,那麼大家刚刚见到的融洽方式就已充足。但是,一旦必须同时共享资源数据信息,以便避免数据信息毁坏,就务必要有附加的同歩。 2个及其大量的进程同时升级一数量据构造时,数据信息迅速便会越来越不一致。就行像下边这一实例编码一样:

var counters = new Dictionary int, int if (counters.ContainsKey(key)){counters[key] ++;}else{counters[key] = 1;}

当好几个进程同时实行所述编码时,不一样进程中的特殊次序实行命令将会造成数据信息歪斜确,比如:

全部进程可能查验结合中是不是存有同一个 key 結果,她们都是进到 else 支系,并将这一 key 的值设成1 最终結果可能是1,而并不是2。假如是连续着实行编码得话,可能是预估的結果。

所述编码中,临界值区 (critical section) 一次只容许一个进程能够进到。在C# 中,可使用 lock 句子来完成:

var counters = new Dictionary int, int lock (syncObject){if (counters.ContainsKey(key)){counters[key]++;}else{counters[key] = 1;}}

在这里个方式中,全部进程都务必共享资源同样的的 syncObject 。做为最好作法,syncObject 应当是一个专用型的 Object 案例,专业用以维护对一个单独的临界值区的浏览,防止由外部浏览。 在 lock 句子中,只容许一个进程浏览里边的编码块。它将阻拦下一个试着浏览它的进程,直至前一个进程撤出。这将保证进程详细实行临界值区编码,而不容易被另外一个进程终断。自然,这将降低并行处理性并缓减编码的总体实行速率,因而您最好最少化临界值区的总数并使其尽量的短。

应用 Monitor 类来简单化 lock 申明:

var lockWasTaken = false;var temp = syncObject;try{Monitor.Enter(temp, ref lockWasTaken);// lock statement body}finally{if (lockWasTaken){Monitor.Exit(temp);}}

虽然大部分分时图间您都期待应用 lock 句子,但 Monitor 类能够在必须时给与附加的操纵。比如,您可使用 TryEnter() 而并不是 Enter(),并特定一个限制時间,防止无止尽地等候锁释放出来。

别的同歩基元

Monitor 仅仅 .NET Core 中诸多同歩基元的一员。依据具体状况,别的基元将会更合适。

Mutex 是 Monitor 更净重级的版本号,依靠于最底层的实际操作系统软件,出示跨好几个过程同歩浏览資源[1], 是对于 Mutex 开展同歩的强烈推荐取代计划方案。

SemaphoreSlim 和 Semaphore 能够限定同时浏览資源的较大进程总数,而并不是像 Monitor 一样只有限定一个进程。 SemaphoreSlim 比 Semaphore 更轻量,但只限于单独过程。假如将会,您最管用用 SemaphoreSlim 而并不是 Semaphore。

ReaderWriterLockSlim 能够区别二种对浏览資源的方法。它容许无尽总数的载入器 (readers) 同时浏览資源,而且限定同时只容许一个载入器 (writers) 浏览锁住資源。载入时进程安全性,但改动数据信息时要要占有資源,非常好地维护了資源。

AutoResetEvent、ManualResetEvent 和 ManualResetEventSlim 将阻塞传到的进程,直至他们接受到一个数据信号 (即启用 Set() )。随后等候中的进程将再次实行。AutoResetEvent 在下一次启用 Set() 以前,将一直堵塞,并只容许一个进程再次实行。ManualResetEvent 和 ManualResetEventSlim 不容易阻塞进程,除非是 Reset() 被启用。ManualResetEventSlim 比前二者更轻量,更非常值得强烈推荐。

Interlocked 出示一种挑选——分子实际操作,它是取代 locking 和别的同歩基元更强的挑选(假如可用):

// non-atomic operation with a locklock (syncObject){counter++;}// equivalent atomic operation that doesn't require a lockInterlocked.Increment(ref counter);

高并发结合

当一个临界值区必须保证多数据构造的分子浏览时,用以高并发浏览的专用型数据信息构造将会是更强和更合理的取代计划方案。比如,应用 ConcurrentDictionary 而并不是 Dictionary,能够简单化 lock 句子实例:

var counters = new ConcurrentDictionary int, int counters.TryAdd(key, 0);lock (syncObject){counters[key]++;}

当然地,也是有将会像下边一样:

counters.AddOrUpdate(key, 1, (oldKey, oldValue) = oldValue + 1);

由于 update 的授权委托是临界值区外边的方式,因而,第二个进程将会在第一个进程升级值以前,载入到一样的旧值,应用自身的值合理地遮盖了第一个进程的升级值,这就遗失了一个增加量。不正确应用高并发结合也是没法防止多段程产生的难题。 高并发结合的另外一个取代计划方案是 不会改变的结合 (immutable collections)。 相近于高并发结合,一样是进程安全性的,可是最底层完成不是一样的。一切关更改数据信息构造的实际操作将不容易更改原先的案例。反过来,他们回到一个变更后的团本,并维持初始案例不会改变:

var original = new Dictionary int, int ().ToImmutableDictionary();var modified = original.Add(key, value);

因而在一个进程中对结合一切变更针对别的进程来讲全是不能见的。由于他们依然引入原先的未改动的结合,这便是不会改变的结合实质上是进程安全性的缘故。 自然,这促使他们针对处理不一样结合的难题很合理。最好的状况是好几个进程在同一个键入结合的状况下,单独地改动数据信息,在最终一步将会为全部进程合拼变动。而应用基本结合,必须提早为每一个进程建立结合的团本。

并行处理LINQ (PLINQ)

并行处理LINQ (PLINQ) 是 Task Parallel Library 的取代计划方案。说白了,它非常大水平上依靠于 LINQ(語言集成化查寻)作用。针对在大结合中实行同样的价格昂贵实际操作的情景是很有效的。与全部实际操作全是次序实行的一般 LINQ to Objects 不一样的是,PLINQ能够在好几个CPU上并行处理实行这种实际操作。 充分发挥优点需要要的编码修改也是很小的:

// sequential executionvar sequential = Enumerable.Range(0, 40).Select(n = ExpensiveOperation(n)).ToArray();// parallel executionvar parallel = Enumerable.Range(0, 40).AsParallel().Select(n = ExpensiveOperation(n)).ToArray();

如你所闻,这2个编码片断的不一样只是是启用 AsParallel()。这将IEnumerable 变换为 ParallelQuery,造成查寻的一部分并行处理运作。要转换为回次序实行,您能够启用 AsSequential(),它将再度回到一个IEnumerable。 默认设置状况下,PLINQ 不保存结合中的次序,便于让过程更合理率。可是当次序太重要时,能够启用 AsOrdered():

var parallel = Enumerable.Range(0, 40).AsParallel().AsOrdered().Select(n = ExpensiveOperation(n)).ToArray();

同样,你可以以根据启用 AsUnordered() 转换回家。

在详细的 .NET Framework 中高并发程序编写

因为 .NET Core 是详细的 .NET Framework 的简单化完成,因此 .NET Framework 中常有并行处理程序编写方式还可以在.NET Core 中应用。唯一的列外不是变的结合,他们并不是详细的 .NET Framework 的构成一部分。他们做为独立的 NuGet 手机软件包(System.Collections.Immutable)派发,您必须在新项目中安裝应用。

结果:

每每运用程序包括能够并行处理运作的 CPU 聚集型编码时,运用高并发程序编写来提升特性并提升硬件配置运用率是很更有意义的。 .NET Core 中的 API 抽象性了很多关键点,使撰写高并发编码更非常容易。但是必须留意一些潜伏的难题, 在其中大部分分涉及到从好几个进程浏览共享资源数据信息。 假如如果可以的话,你应当彻底防止这类状况。假如不好,请保证挑选最好的同歩方式或数据信息构造。

之上便是文中的所有內容,期待对大伙儿的学习培训有一定的协助,也期待大伙儿多多的适用脚本制作之家。



联系我们

全国服务热线:4000-399-000 公司邮箱:343111187@qq.com

  工作日 9:00-18:00

关注我们

官网公众号

官网公众号

Copyright?2020 广州凡科互联网科技股份有限公司 版权所有 粤ICP备10235580号 客服热线 18720358503

技术支持:怎么建网站