mastering c# concurrency - ebooksworld...a print book customer, you are entitled to a discount on...

388
www.EBooksWorld.ir

Upload: others

Post on 29-May-2020

1 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 2: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 3: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

MasteringC#Concurrency

www.EBooksWorld.ir

Page 4: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TableofContents

MasteringC#Concurrency

Credits

AbouttheAuthors

AbouttheReviewers

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmore

Whysubscribe?

FreeaccessforPacktaccountholders

InstantupdatesonnewPacktbooks

Preface

Whatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

Conventions

Readerfeedback

Customersupport

Downloadingtheexamplecode

Errata

Piracy

Questions

1.TraditionalConcurrency

What’stheproblem?

Usinglocks

Lockstatement

Monitorclass

Reader-writerlock

Spinlock

Thread.SpinWait

System.Threading.SpinWait

System.Threading.SpinLock

www.EBooksWorld.ir

Page 5: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Optimizationstrategy

Locklocalization

Shareddataminimization

Summary

2.Lock-FreeConcurrency

Memorymodelandcompileroptimizations

TheSystem.Threading.Interlockedclass

Interlockedinternals

Writinglock-freecode

TheABAproblem

Thelock-freestack

Thelock-freequeue

Summary

3.UnderstandingParallelismGranularity

Thenumberofthreads

Usingthethreadpool

Understandinggranularity

Choosingthecoarse-grainedorfine-grainedapproach

Summary

4.TaskParallelLibraryinDepth

Taskcomposition

Taskshierarchy

Awaitingtaskcompletion

Taskcancellation

Checkingaflag

Throwinganexception

UsingOSwaitobjectswithWaitHandle

Cancellationusingcallbacks

Latencyandthecoarse-grainedapproachwithTPL

Exceptionhandling

UsingtheParallelclass

Parallel.Invoke

www.EBooksWorld.ir

Page 6: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Parallel.ForandParallel.Foreach

Understandingthetaskscheduler

Summary

5.C#LanguageSupportforAsynchrony

ImplementingthedownloadingofimagesfromBing

Creatingasimplesynchronoussolution

CreatingaparallelsolutionwithTaskParallelLibrary

EnhancingthecodewithC#5.0built-insupportforasynchrony

SimulatingC#asynchronousinfrastructurewithiterators

Istheasynckeywordreallyneeded?

Fire-and-forgettasks

OtherusefulTPLfeatures

Task.Delay

Task.Yield

Implementingacustomawaitabletype

Summary

6.UsingConcurrentDataStructures

Standardcollectionsandsynchronizationprimitives

ImplementingacachewithReaderWriterLockSlim

Concurrentcollectionsin.NET

ConcurrentDictionary

UsingLazy<T>

Implementationdetails

Lock-freeoperations

Fine-grainedlockoperations

Exclusivelockoperations

Usingtheimplementationdetailsinpractice

ConcurrentBag<T>

ConcurrentBaginpractice

ConcurrentQueue<T>

ConcurrentStack<T>

TheProducer/Consumerpattern

www.EBooksWorld.ir

Page 7: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CustomProducer/Consumerpatternimplementation

TheProducer/Consumerpatternin.NET4.0+

Summary

7.LeveragingParallelPatterns

Concurrentidioms

ProcessTasksinCompletionOrder

Limitingtheparallelismdegree

Settingatasktimeout

Asynchronouspatterns

AsynchronousProgrammingModel

Event-basedAsynchronousPattern

Task-basedAsynchronousPattern

Concurrentpatterns

Parallelpipelines

Summary

8.Server-sideAsynchrony

Serverapplications

TheOWINWebAPIframework

Loadtestingandscalability

I/OandCPU-boundtasks

DeepdiveintoasynchronousI/O

RealandfakeasynchronousI/Ooperations

Synchronizationcontext

CPU-boundtasksandqueues

Summary

9.ConcurrencyintheUserInterface

TheimportanceofasynchronyforUI

UIthreadsandmessageloops

Commonproblemsandsolutions

Howtheawaitkeywordworks

Executionandsynchronizationcontexts

Performanceissues

www.EBooksWorld.ir

Page 8: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Summary

10.TroubleshootingParallelPrograms

Howtroubleshootingparallelprogramsisdifferent

Heisenbugs

Writingtests

Loadtests

Unittests

Integrationtests

Debugging

Justmycodesetting

Callstackwindow

Threadswindow

Taskswindow

Parallelstackswindow

Performancemeasurementandprofiling

TheConcurrencyVisualizer

Summary

Index

www.EBooksWorld.ir

Page 9: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 10: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

MasteringC#Concurrency

www.EBooksWorld.ir

Page 11: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 12: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

MasteringC#ConcurrencyCopyright©2015PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthors,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:October2015

Productionreference:1231015

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78528-665-0

www.packtpub.com

www.EBooksWorld.ir

Page 13: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 14: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CreditsAuthors

EugeneAgafonov

AndrewKoryavchenko

Reviewers

TimGabrhel

MichaelBerantzinoHansen

GürayÖzen

SimonSoanes

AcquisitionEditor

ReshmaRaman

ContentDevelopmentEditor

ZeeyanPinheiro

TechnicalEditor

MenzaMathew

CopyEditors

KausambhiMajumdar

AlphaSingh

ProjectCoordinator

SuzanneCoutinho

Proofreader

SafisEditing

Indexer

RekhaNair

ProductionCoordinator

MelwynDsa

CoverWork

MelwynDsa

www.EBooksWorld.ir

Page 15: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 16: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

AbouttheAuthorsEugeneAgafonovleadstheLingvoLivedevelopmentdepartmentatABBYY,andhelivesandworksinMoscow.Hehasover15yearsofprofessionalexperienceinsoftwaredevelopmentandhasbeenworkingwithC#eversinceitwasinbetaversion.HehasbeenaMicrosoftMVPinASP.NETsince2006,andheoftenspeaksatlocalsoftwaredevelopmentconferences,suchasDevConRussia,aboutcutting-edgetechnologiesinmodernwebandserver-sideapplicationdevelopment.Hismainprofessionalinterestsarecloud-basedsoftwarearchitecture,scalability,andreliability.Eugeneisahugefanoffootballandplaystheguitarwithalocalrockband.Youcanreachhimathispersonalblogateugeneagafonov.comorhisTwitterhandleat@eugene_agafonov.

HealsowroteMultithreadinginC#5.0CookbookbyPacktPublishing.

IwouldliketothankSergeyTeplyakov,whoisasupercoolMicrosoftguyandhasanultimatetwitteraccountat@STeplyakov,forhelpingmealotinwritingchapters6and7,andhisinvaluableadvicethatallowedmetomakethisbookbetter.

AndrewKoryavchenkoisasoftwaredeveloperandanarchitectwholivesinMoscow,Russia.Heisoneofthefoundersofrsdn.ru—thelargestRussiansoftwaredevelopers’communityportal.

HisspecialtyisERPsystemsanddevelopertools.HeparticipatedinReSharperVisualStudioextensiondevelopment,whichisawell-knownproductivitytoolfor.NETdevelopers.Currently,heisworkingonparsingandcompilationtoolsfor.NETdevelopmentandalsosupportsanddevelopsthersdn.ruportal.

AndrewregularlyspeaksatonlineandofflineeventsandconferencesdedicatedtoMicrosofttechnologies,andhepublishesarticlesonsoftwaredevelopmenttopics.HealsousedtoteachEnterpriseSoftwareDevelopmentcourseinKubanStateUniversity.

AndrewhasbeenaMicrosoftMVPinC#since2005.

www.EBooksWorld.ir

Page 17: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 18: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

AbouttheReviewersTimGabrhelisaseniorapplicationdeveloperatConcurrencyInc.,withacorefocusonMicrosoftAzureandmodern.NETtechnologies.Heisacreatorandmakerandlovesbeinghands-onwithnewtechnologiesandmakingthemworkinreallife.TimhasbeenaconsultantforFortune100companies.Hehascontributedtothearchitectureandkeycomponentsofenterprisesolutionsthathavereachedhundredsofthousandsofusersaroundtheworld.YoucanfollowTimandhistechnicaljourneyathisblog,http://timgabrhel.com.

MichaelBerantzinoHansenisaMCPD.NETEnterpriseApplicationDeveloperspecializinginhighperformanceandefficientframeworks.Hehasbeenprogrammingsincethemid80sfromtheageof9.HestartedwithBasicandthenmovedontoC++.In1999,heearnedabachelorsdegreeincomputerscience,economics,andorganizationaldevelopment,whileworkingparttimeforground-breakingstartups.In2005,hemovedontoC#ashispreferredplatform.Michaelexcelsindevelopingcomplexframeworks,algorithms,andapplications.Hedoesfullstackdevelopmentusingmoderntechnologies.HerecentlyadoptedTypeScriptashispreferredplatformforclient-sidewebdevelopment.

MichaelcurrentlyworksasachiefsystemdeveloperintheSPAMfighter,developingcomplexe-mailanalysisplatformsresponsibleforallenterprisesolutionsinSPAMfighter.

GürayÖzenhasbeenworkingasaresearchfellowintheprogrammingmodelsteamatBarcelonaSupercomputingCenter(BSC)sinceAugust2013.HisworkisalsopartofhisPhDresearchthatexplorescompiler-basedparallelismandoptimizationsforheterogeneoussystems.Besidesthis,hiscurrentresearchinterestsconsistoftheprinciplesofprogramminglanguagesandparallelprogramming.Hereceivedamaster’sdegreeinhighperformancecomputingfromtheDepartmentofComputerArchitectureatUniversitatPolitècnicadeCatalunya–BarcelonaTechin2014.In2010and2012,heworkedatoneofthebiggestbanksinTurkeyasaC#-backedapplicationsdeveloper.Hehasabachelor’sdegreeincomputerscienceengineeringfromDokuzEylulUniveristyinIzmir,Turkey.

SimonSoanesisasoftwaredeveloperwithabackgroundinnetworkingtechnologies,databases,distributedsystems,anddebugging.EversincethedaysoftheC64,hehasenjoyedwritingsoftware,playingcomputergames,andmakingdevicescommunicatewitheachotherincreativeways.

He’scurrentlyworkinginthesouthofEnglandasacontractor.Atsomepoint,hebecameaddictedtosolvingtechnicalproblemsandautomatingthings.

Heoccasionallywritesablogathttp://www.nullify.net/.

www.EBooksWorld.ir

Page 19: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 20: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.PacktPub.com

www.EBooksWorld.ir

Page 21: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www2.packtpub.com/books/subscription/packtlib

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.

www.EBooksWorld.ir

Page 22: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

www.EBooksWorld.ir

Page 23: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

www.EBooksWorld.ir

Page 24: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

InstantupdatesonnewPacktbooksGetnotified!Findoutwhennewbooksarepublishedbyfollowing@PacktEnterpriseonTwitterorthePacktEnterpriseFacebookpage.

ToMomandDad—youarethebestparentsonEarthandIloveyousomuch.

www.EBooksWorld.ir

Page 25: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 26: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

PrefaceRecentC#and.NETdevelopmentsinvolveimplicitlyusingasynchronyandconcurrency,evenwhenyouarenotawareofthem.ThiscanleadtofurtherproblemssincemanydetailsareusuallyhiddeninsidetheC#languageinfrastructureandthe.NETbaseclasslibraryAPIs.Toavoidproblemsandtobeabletocreaterobustapplications,adeveloperhastoknowexactlywhatisgoingonunderthehoodofasynchronyin.NET.

Besidesthis,itisimportanttounderstandyourgoalswhenwritingaconcurrentapplication.Ifitisrunningontheclient,itisusuallyagoodthingtouseallthecomputationalresourcesavailablesothattheapplicationbecomesasfastaspossible.ThisinvolveseffectivemultipleCPUcoresusage,andthusrequiresparallelprogrammingskills.However,iftheapplicationisrunningontheserver,itismoreimportantthattheserversupportsasmanyclientsaspossible,thantheperformanceofaconcreteclientrequestprocessing.Thisrequiresaprogrammertodistinguishasynchronyfrommultithreadingandhaveanunderstandingofscalability.

Allthesetopicswillbecoveredinthisbook,providingyouwithenoughinformationtoachieveasolidunderstandingofasynchronousandparallelprogramminginC#.Wewillstartwithbasicmultithreadingconcepts,reviewcommonconcurrentprogrammingproblemsandsolutions,andthenwewillgothroughC#and.NETsupportforwritingconcurrentapplications.Furtherinthebook,wewillcoverconcurrentdatastructuresandpatterns,andwewillreviewclient-sideandserver-sideconcurrencyissues.Attheendofthebook,wewilloutlinethebasicprinciplesforcreatingrobustconcurrentprograms.

www.EBooksWorld.ir

Page 27: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

WhatthisbookcoversChapter1,TraditionalConcurrency,coverscommonproblemswithmultithreadingandsolutionstotheseproblems.Youwillrefreshyourknowledgeaboutbasiclockingtechniquesandhowtomakelockingmoreefficient.

Chapter2,Lock-FreeConcurrency,goesfurtherintoperformanceoptimization.Itcoversvariouswaystowriteconcurrentprogramswithoutlocking,makingthecodefastandreliable.

Chapter3,UnderstandingParallelismGranularity,explainsanotherimportantaspectoforganizingyourparallelcode—splittingacomputationalworkloadbetweenthreads.Itintroducescoarse-grainedandfine-grainedapproaches,showingtheirprosandcons.

Chapter4,TaskParallelLibraryinDepth,goesintothedetailsofTaskParallelLibrary—aframeworktoorganizeyourconcurrentprogramasasetofrelatedtasks.YouwillfindtheinternalsofTPLreviewedandexplained.

Chapter5,C#LanguageSupportforAsynchrony,isadeepdiveintotheC#languageinfrastructure.Thechaptershowsexactlyhowtheasyncandawaitkeywordsworkandhowyoucanwriteyourownawait-compatiblecode.

Chapter6,UsingConcurrentDataStructures,coverstheuseofdatastructuresinaconcurrentprogramindetail,includingstandard.NETconcurrentcollectionsandcustomthreadsafecollectionsimplementations.

Chapter7,LeveragingParallelPatterns,reviewsprogrammingpatternsrelatedtoparallelapplications.Thechapterdescribesdifferentkindsofpatterns—historical.NETidioms,usefulcodesnippets,andahigh-levelparallelpipelinepattern.

Chapter8,Server-SideAsynchrony,isasolutiondescriptiontotheproblemofusingasynchronyontheserver.Itexplainswhyitisveryimportanttodistinguishasynchronyfromparallelism,andhowitcanaffectthescalabilityandreliabilityofyourserver.

Chapter9,ConcurrencyintheUserInterface,describesthedetailsofhowtheuserinterfaceisimplemented,whatamessageloopis,andwhyitisveryimportanttokeeptheUIthreadnonblocked.

Chapter10,TroubleshootingParallelPrograms,explainshowtofindoutwhatiswrongwithyourparallelprogram.Youwilllearnhowtowriteunittestsforanasynchronouscode,howtodebugit,andfindperformancebottlenecks.

www.EBooksWorld.ir

Page 28: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 29: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

WhatyouneedforthisbookYouwillneedVisualStudio2013or2015torunthecodesamples.Formostofthechapters,itwillbeenoughtousethefreeVisualStudioCommunity2013/2015editions,buttheperformancetestsampleswillrequiretheTest/UltimateorEnterpriseeditions.However,ifyoucannotusethis,itispossibletodownloadthefreeApachebenchtooltorunperformancetestsasdescribedinthebook.

www.EBooksWorld.ir

Page 30: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 31: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

WhothisbookisforMasteringC#ConcurrencyiswrittenforexistingC#developerswhohaveaknowledgeofbasicmultithreadingconceptsandwanttoimprovetheirasynchronousandparallelprogrammingskills.Thebookcoversdifferenttopics,frombasicconceptstocomplicatedprogrammingpatternsandalgorithmsusingtheC#and.NETecosystems.Thiswillbeusefultoserverandclientdevelopers,becauseitcoversalltheimportantaspectsofusingconcurrencyandasynchronyonbothsides.

www.EBooksWorld.ir

Page 32: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 33: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConventionsInthisbook,youwillfindanumberofstylesoftextthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestyles,andanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“ThishappensbecausetheAddmethodoftheList<T>classisnotthreadsafe,andthereasonforthisliesintheimplementationdetails.”

Ablockofcodeissetasfollows:

publicvoidAdd(Titem)

{

if(_size==_items.Length)EnsureCapacity(_size+1);

_items[_size++]=item;

_version++;

}

Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

publicvoidAdd(Titem)

{

if(_size==_items.Length)EnsureCapacity(_size+1);

_items[_size++]=item;

_version++;

}

Anycommand-lineinputoroutputiswrittenasfollows:

T2:Add-[T2]:Item1

T1:Add-[T1]:Item1

T2:Add-[T2]:Item2

T2:Add-[T2]:Item3

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,inmenusordialogboxesforexample,appearinthetextlikethis:“ClickonFinishandrepeatallthisforanothercontroller.”

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

www.EBooksWorld.ir

Page 34: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 35: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedormayhavedisliked.Readerfeedbackisimportantforustodeveloptitlesthatyoureallygetthemostoutof.

Tosendusgeneralfeedback,simplysendane-mailto<[email protected]>,andmentionthebooktitleviathesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideonwww.packtpub.com/authors.

www.EBooksWorld.ir

Page 36: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 37: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

www.EBooksWorld.ir

Page 38: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

www.EBooksWorld.ir

Page 39: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyouwouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheerratasubmissionformlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedonourwebsite,oraddedtoanylistofexistingerrata,undertheErratasectionofthattitle.Anyexistingerratacanbeviewedbyselectingyourtitlefromhttp://www.packtpub.com/support.

www.EBooksWorld.ir

Page 40: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

PiracyPiracyofcopyrightmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.Ifyoucomeacrossanyillegalcopiesofourworks,inanyform,ontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluablecontent.

www.EBooksWorld.ir

Page 41: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

QuestionsYoucancontactusat<[email protected]>ifyouarehavingaproblemwithanyaspectofthebook,andwewilldoourbesttoaddressit.

www.EBooksWorld.ir

Page 42: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 43: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter1.TraditionalConcurrencySpeakingofconcurrency,wehavetostarttalkingaboutthreads.Ironically,thereasonbehindimplementingthreadswastoisolateprogramsfromeachother.BackintheearlydaysofWindows,versions3.*usedcooperativemultitasking.Thismeantthattheoperatingsystemexecutedalltheprogramsonasingleexecutionloop,andifoneofthoseprogramshung,everyotherprogramandtheoperatingsystemitselfwouldstoprespondingaswellandthenitwouldberequiredtorebootthemachinetoresolvethisproblem.

Tocreateamorerobustenvironment,theOShadtolearnhowtogiveeveryprogramitsownpieceofCPU,soifoneprogramenteredaninfiniteloop,theotherswouldstillbeabletousetheCPUfortheirownneeds.Athreadisanimplementationofthisconcept.Thethreadsallowimplementingpreemptivemultitasking,whereinsteadoftheapplicationdecidingwhentoyieldcontroltoanotherapplication,theOScontrolshowmuchCPUtimetogivetoeachapplication.

WhenCPUsstartedtohavemultiplecores,itbecamemorebeneficialtomakefulluseofthecomputationalcapabilityavailable.Theuseofthethreadsdirectlybyapplicationssuddenlybecamemoreworthwhile.However,whenexploringmultithreadingissues,suchashowtosharethedatabetweenthethreadssafely,theset-uptimeofthethreadsimmediatelybecomeevident.

Inthischapter,wewillconsiderthebasicconcurrentprogrammingpitfallsandthetraditionalapproachtodealwiththem.

www.EBooksWorld.ir

Page 44: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

What’stheproblem?Simplyusingmultiplethreadsinaprogramisnotaverycomplicatedtask.Ifyourprogramcanbeeasilyseparatedintoseveralindependenttasks,thenyoujustrunthemindifferentthreads,andthesethreadscanbescaledalongwiththenumberofCPUcores.However,usuallyrealworldprogramsrequiresomeinteractionbetweenthesethreads,suchasexchanginginformationtocoordinatetheirwork.Thiscannotbeimplementedwithoutsharingsomedata,whichrequiresallocatingsomeRAMspaceinsuchawaythatitisaccessiblefromallthethreads.Dealingwiththissharedstateistherootofalmosteveryproblemrelatedtoparallelprogramming.

Thefirstcommonproblemwithsharedstateisundefinedaccessorder.Ifwehavereadandwriteaccess,thisleadstoincorrectcalculationresults.Thissituationiscommonlyreferredtoasaracecondition.

Followingisasampleofaracecondition.Wehaveacounter,whichisbeingchangedfromdifferentthreadssimultaneously.Eachthreadincrementsthecounter,thendoessomework,andthendecrementsthecounter.

constintiterations=10000;

varcounter=0;

ThreadStartproc=()=>{

for(inti=0;i<iterations;i++){

counter++;

Thread.SpinWait(100);

counter--;

}

};

varthreads=Enumerable

.Range(0,8)

.Select(n=>newThread(proc))

.ToArray();

foreach(varthreadinthreads)

thread.Start();

foreach(varthreadinthreads)

thread.Join();

Console.WriteLine(counter);

Theexpectedcountervalueis0.However,whenyouruntheprogram,yougetdifferentnumbers(whichisusuallynot0,butitcouldbe)eachtime.Thereasonisthatincrementinganddecrementingthecounterisnotanatomicoperation,butconsistsofthreeseparatesteps–readingthecountervalue,incrementingordecrementingthisvalue,andwritingtheresultbackintothecounter.

Letusassumethatwehaveinitialcountervalue0,andtwothreads.Thefirstthreadreads0,incrementsitto1,andwrites1intothecounter.Thesecondthreadreads1fromthecounter,incrementsitto2,andthenwrites2intothecounter.Thisseemstobecorrectandisexactlywhatweexpected.Thisscenarioisrepresentedinthefollowingdiagram:

www.EBooksWorld.ir

Page 45: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Nowthefirstthreadreads2fromthecounter,andatthesametimeitdecrementsitto1;thesecondthreadreads2fromthecounter,becausethefirstthreadhasn’twritten1intothecounteryet.Sonow,thefirstthreadwrites1intothecounter,andthesecondthreaddecrements2to1andwritesthevalue1intothecounter.Asaresult,wehavethevalue1,whilewe’reexpecting0.Thisscenarioisrepresentedinthefollowingdiagram:

TipDownloadingtheexamplecode

Youcandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Toavoidthis,wehavetorestrictaccesstothecountersothatonlyonethreadreadsitatatime,calculatestheresult,andwritesitback.Sucharestrictioniscalledalock.However,byusingittoresolvearaceconditionproblem,wecreateotherpossibilitiesforourconcurrentcodetofail.Withsucharestriction,weturnourparallelprocessintoasequentialprocess,whichinturnmeansthatourcoderunslessefficiently.Themoretimethecoderunsinsidethelock,thelessefficientandscalablethewholeprogramis.Thisisbecausethelockheldbyonethreadblockstheotherthreadsfromperformingtheirwork,therebymakingthewholeprogramtakelongertorun.So,wehavetominimizethelocktimetokeeptheotherthreadsrunning,insteadofwaitingforthelocktobereleasedto

www.EBooksWorld.ir

Page 46: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

startdoingtheircalculations.

Anotherproblemrelatedtolocksisbestillustratedbythefollowingexample.Itshowstwothreadsusingtworesources,AandB.ThefirstthreadneedstolockobjectAfirst,thenB,whilethesecondthreadstartswithlockingBandthenA.

constintcount=10000;

vara=newobject();

varb=newobject();

varthread1=

newThread(

()=>

{

for(inti=0;i<count;i++)

lock(a)

lock(b)

Thread.SpinWait(100);

});

varthread2=

newThread(

()=>

{

for(inti=0;i<count;i++)

lock(b)

lock(a)

Thread.SpinWait(100);

});

thread1.Start();

thread2.Start();

thread1.Join();

thread2.Join();

Console.WriteLine("Done");

Itlookslikethiscodeisalright,butifyourunitseveraltimes,itwilleventuallyhang.Thereasonforthisliesinanissuewiththelockingorder.IfthefirstthreadlocksA,andthesecondlocksBbeforethefirstthreaddoes,thenthesecondthreadstartswaitingforthelockonAtobereleased.However,toreleasethelockonA,thefirstthreadneedstoputalockonB,whichisalreadylockedbythesecondthread.Therefore,boththethreadswillwaitforeverandtheprogramwillhang.

Suchasituationiscalledadeadlock.Itisusuallyquitehardtodiagnosedeadlocks,becauseitishardtoreproduceone.

NoteThebestwaytoavoiddeadlocksistotakepreventivemeasureswhenwritingcode.Thebestpracticeistoavoidcomplicatedlockstructuresandnestedlocks,andminimizethetimeinlocks.Ifyoususpecttherecouldbeadeadlock,thenthereisanotherwaytopreventitfromhappening,whichisbysettingatimeoutforacquiringalock.

www.EBooksWorld.ir

Page 47: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 48: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UsinglocksTherearedifferenttypesoflocksinC#and.NET.Wewillcovertheselaterinthechapter,andalsothroughoutthebook.LetusstartwiththemostcommonwaytousealockinC#,whichisalockstatement.

www.EBooksWorld.ir

Page 49: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

LockstatementLockstatementinC#usesasingleargument,whichcouldbeaninstanceofanyclass.Thisinstancewillrepresentthelockitself.

Readingotherpeople’scodes,youcouldseethatalockusestheinstanceofcollectionorclass,whichcontainsshareddata.Itisnotagoodpractice,becausesomeoneelsecouldusethisobjectforlocking,andpotentiallycreateadeadlocksituation.So,itisrecommendedtouseaspecialprivatesynchronizationobject,thesolepurposeofwhichistoserveasaconcretelock:

//Bad

lock(myCollection){

myCollection.Add(data);

}

//Good

lock(myCollectionLock){

myCollection.Add(data);

}`

NoteItisdangeroustouselock(this)andlock(typeof(MyType)).Thebasicideawhyitisbadremainsthesame:theobjectsyouarelockingcouldbepubliclyaccessible,andthussomeoneelsecouldacquirealockonitcausingadeadlock.However,usingthethiskeywordmakesthesituationmoreimplicit;ifsomeoneelsemadetheobjectpublic,itwouldbeveryhardtotrackthatitisbeingusedinsidealock.

Lockingthetypeobjectisevenworse.Inthecurrentversionsof.NET,theruntimetypeobjectscouldbesharedacrossapplicationdomains(runninginthesameprocess).Itispossiblebecausethoseobjectsareimmutable.However,thismeansthatadeadlockcouldbecaused,notonlybyanotherthread,butalsobyANOTHERAPPLICATION,andIbetthatyouwouldhardlyunderstandwhat’sgoingoninsuchacase.

FollowingishowwecanrewritethefirstexamplewithraceconditionandfixitusingC#lockstatement.Nowthecodewillbeasfollows:

constintiterations=10000;

varcounter=0;

varlockFlag=newobject();

ThreadStartproc=()=>{

for(inti=0;i<iterations;i++)

{

lock(lockFlag)

counter++;

Thread.SpinWait(100);

lock(lockFlag)

counter--;

}

};

varthreads=Enumerable

.Range(0,8)

.Select(n=>newThread(proc))

www.EBooksWorld.ir

Page 50: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

.ToArray();

foreach(varthreadinthreads)

thread.Start();

foreach(varthreadinthreads)

thread.Join();

Console.WriteLine(counter);

Nowthiscodeworksproperly,andtheresultisalways0.

Tounderstandwhatishappeningwhenalockstatementisusedintheprogram,letuslookattheIntermediateLanguagecode,whichisaresultofcompilingC#program.ConsiderthefollowingC#code:

staticvoidMain()

{

varctr=0;

varlockFlag=newobject();

lock(lockFlag)

ctr++;

}

Theprecedingblockofcodewillbecompiledintothefollowing:

.methodprivatehidebysigstaticvoidMain()cilmanaged{

.entrypoint

//Codesize48(0x30)

.maxstack2

.localsinit([0]int32ctr,

[1]objectlockFlag,

[2]bool'<>s__LockTaken0',

[3]objectCS$2$0000,

[4]boolCS$4$0001)

IL_0000:nop

IL_0001:ldc.i4.0

IL_0002:stloc.0

IL_0003:newobjinstancevoid[mscorlib]System.Object::.ctor()

IL_0008:stloc.1

IL_0009:ldc.i4.0

IL_000a:stloc.2

.try

{

IL_000b:ldloc.1

IL_000c:dup

IL_000d:stloc.3

IL_000e:ldloca.s'<>s__LockTaken0'

IL_0010:callvoid

[mscorlib]System.Threading.Monitor::Enter(object,bool&)

IL_0015:nop

IL_0016:ldloc.0

IL_0017:ldc.i4.1

IL_0018:add

IL_0019:stloc.0

IL_001a:leave.sIL_002e

}//end.try

finally

{

IL_001c:ldloc.2

www.EBooksWorld.ir

Page 51: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

IL_001d:ldc.i4.0

IL_001e:ceq

IL_0020:stloc.sCS$4$0001

IL_0022:ldloc.sCS$4$0001

IL_0024:brtrue.sIL_002d

IL_0026:ldloc.3

IL_0027:callvoid

[mscorlib]System.Threading.Monitor::Exit(object)

IL_002c:nop

IL_002d:endfinally

}//endhandler

IL_002e:nop

IL_002f:ret

}//endofmethodProgram::Main

ThiscanbeexplainedwithdecompilationtoC#.Itwilllooklikethis:

staticvoidMain()

{

varctr=0;

varlockFlag=newobject();

boollockTaken=false;

try

{

System.Threading.Monitor.Enter(lockFlag,reflockTaken);

ctr++;

}

finally

{

if(lockTaken)

System.Threading.Monitor.Exit(lockFlag);

}

}

ItturnsoutthatthelockstatementturnsintocallingtheMonitor.EnterandMonitor.Exitmethods,wrappedintoatry-finallyblock.TheEntermethodacquiresanexclusivelockandreturnsaboolvalue,indicatingthatalockwassuccessfullyacquired.Ifsomethingwentwrong,forexampleanexceptionhasbeenthrown,theboolvaluewouldbesettofalse,andtheExitmethodwouldreleasetheacquiredlock.

Atry-finallyblockensuresthattheacquiredlockwillbereleasedevenifanexceptionoccursinsidethelockstatement.IftheEntermethodindicatesthatwecannotacquirealock,thentheExitmethodwillnotbeexecuted.

www.EBooksWorld.ir

Page 52: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

MonitorclassTheMonitorclasscontainsotherusefulmethodsthathelpustowriteconcurrentcode.OneofsuchmethodsistheTryEntermethod,whichallowstheprovisionofatimeoutvaluetoit.Ifalockcouldnotbeobtainedbeforethetimeoutisexpired,theTryEntermethodwouldreturnfalse.Thisisquiteanefficientmethodtopreventdeadlocks,butyouhavetowritesignificantlymorecode.

ConsiderthepreviousdeadlocksamplerefactoredinawaythatoneofthethreadsusesMonitor.TryEnterinsteadoflock:

staticvoidMain()

{

constintcount=10000;

vara=newobject();

varb=newobject();

varthread1=newThread(

()=>{

for(inti=0;i<count;i++)

lock(a)

lock(b)

Thread.SpinWait(100);

});

varthread2=newThread(()=>LockTimeout(a,b,count));

thread1.Start();

thread2.Start();

thread1.Join();

thread2.Join();

Console.WriteLine("Done");

}

staticvoidLockTimeout(objecta,objectb,intcount)

{

boolaccquiredB=false;

boolaccquiredA=false;

constintwaitSeconds=5;

constintretryCount=3;

for(inti=0;i<count;i++)

{

intretries=0;

while(retries<retryCount)

{

try

{

accquiredB=Monitor.TryEnter(b,

TimeSpan.FromSeconds(waitSeconds));

if(accquiredB){

try{

accquiredA=Monitor.TryEnter(a,

TimeSpan.FromSeconds(waitSeconds));

if(accquiredA){

Thread.SpinWait(100);

break;

}

www.EBooksWorld.ir

Page 53: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

else{

retries++;

}

}

finally{

if(accquiredA){

Monitor.Exit(a);

}

}

}

else{

retries++;

}

}

finally{

if(accquiredB)

Monitor.Exit(b);

}

}

if(retries>=retryCount)

Console.WriteLine("couldnotobtainlocks");

}

}

IntheLockTimeoutmethod,weimplementedaretrystrategy.Foreachloopiteration,wetrytoacquirelockBfirst,andifwecannotdosoin5seconds,wetryagain.IfwehavesuccessfullyacquiredlockB,thenweinturntrytoacquirelockA,andifwewaitforitformorethan5seconds,wetryagaintoacquireboththelocks.ThisguaranteesthatifsomeonewaitsendlesslytoacquirealockonB,thenthisoperationwilleventuallysucceed.

IfwedonotsucceedacquiringlockB,thenwetryagainforadefinednumberofattempts.Theneitherwesucceed,orweadmitthatwecannotobtaintheneededlocksandgotothenextiteration.

Inaddition,theMonitorclasscanbeusedtoorchestratemultiplethreadsintoaworkflowwiththeWait,Pulse,andPulseAllmethods.WhenamainthreadcallstheWaitmethod,thecurrentlockisreleased,andthethreadisblockeduntilsomeotherthreadcallsthePulseorPulseAllmethods.Thisallowsthecoordinationthedifferentthreadsexecutionintosomesortofsequence.

Asimpleexampleofsuchworkflowiswhenwehavetwothreads:themainthreadandanadditionalthreadthatperformssomecalculation.Wewouldliketopausethemainthreaduntilthesecondthreadfinishesitswork,andthengetbacktothemainthread,andinturnblockthisadditionalthreaduntilwehaveotherdatatocalculate.Thiscanbeillustratedbythefollowingcode:

vararg=0;

varresult="";

varcounter=0;

varlockHandle=newobject();

varcalcThread=newThread(()=>{

while(true)

lock(lockHandle)

www.EBooksWorld.ir

Page 54: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

counter++;

result=arg.ToString();

Monitor.Pulse(lockHandle);

Monitor.Wait(lockHandle);

}

})

{

IsBackground=true

};

lock(lockHandle)

{

calcThread.Start();

Thread.Sleep(100);

Console.WriteLine("counter={0},result={1}",counter,result);

arg=123;

Monitor.Pulse(lockHandle);

Monitor.Wait(lockHandle);

Console.WriteLine("counter={0},result={1}",counter,result);

arg=321;

Monitor.Pulse(lockHandle);

Monitor.Wait(lockHandle);

Console.WriteLine("counter={0},result={1}",counter,result);

}

Asaresultofrunningthisprogram,wewillgetthefollowingoutput:

counter=0,result=

counter=1,result=123

counter=2,result=321

Atfirst,westartacalculationthread.Thenweprinttheinitialvaluesforcounterandresult,andthenwecallPulse.Thisputsthecalculationthreadintoaqueuecalledreadyqueue.Thismeansthatthisthreadisreadytoacquirethislockassoonasitgetsreleased.ThenwecalltheWaitmethod,whichreleasesthelockandputsthemainthreadintoawaitingqueue.Thefirstthreadinthereadyqueue,whichisourcalculationthread,acquiresthelockandstartstowork.Aftercompletingitscalculations,thesecondthreadcallsPulse,whichmovesathreadattheheadofthewaitingqueue(whichisourmainthread)intothereadyqueue.Ifthereareseveralthreadsinthewaitingqueue,onlythefirstonewouldgointothereadyqueue.Toputallthethreadsintothereadyqueueatonce,wecouldusethePulseAllmethod.So,whenthesecondthreadcallsWait,ourmainthreadreacquiresthelock,changesthecalculationdata,andrepeatsthewholeprocessonemoretime.

NoteNotethatwecanusetheWait,Pulse,andPulseAllmethodsonlywhenthecurrentthreadownsalock.TheWaitmethodcouldblockindefinitelyincasenootherthreadscallPulseorPulseAll,soitcanbeareasonforadeadlock.Topreventdeadlocks,wecanspecifyatimeoutvaluetotheWaitmethodtobeabletoreactincasewecannotreacquirethelockforacertaintimeperiod.

www.EBooksWorld.ir

Page 55: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 56: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Reader-writerlockItisverycommontoseesamplesofcodewherethesharedstateisoneofthestandard.NETcollections:List<T>orDictionary<K,V>.Thesecollectionsarenotthreadsafe;thusweneedsynchronizationtoorganizeconcurrentaccess.

Therearespecialconcurrentcollectionsthatcanbeusedinsteadofthestandardlistanddictionarytoachievethreadsafety.WewillreviewtheminChapter6,UsingConcurrentDataStructures.Fornow,letusassumethatwehavereasonstoorganizeconcurrentaccessbyourselves.

Theeasiestwaytoachievesynchronizationistousethelockoperatorwhenreadingandwritingfromthesecollections.However,theMSDNdocumentationstatesthatifacollectionisnotmodifiedwhilebeingread,synchronizationisnotrequired:

ItissafetoperformmultiplereadoperationsonaList<T>,butissuescanoccurifthecollectionismodifiedwhileit’sbeingread.

AnotherimportantMSDNpagestatesthefollowingregardingacollection:

ADictionary<TKey,TValue>cansupportmultiplereadersconcurrently,aslongasthecollectionisnotmodified.

Thismeansthatwecanperformthereadoperationsfrommultiplethreadsifthecollectionisnotbeingmodified.Thisallowsustoavoidexcessivelocking,andminimizesperformanceoverheadandpossibledeadlocksinsuchsituations.

Toleveragethis,thereisastandard.NETFrameworkclass,System.Threading.ReaderWriterLock.Itprovidesthreetypesoflocks:toreadsomethingfromaresource,towritesomething,andaspecialonetoupgradethereaderlocktoawriterlock.Thefollowingmethodpairsrepresenttheselocks:AcquireReaderLock/ReleaseReaderLock,AcquireWriterLock/ReleaseWriterLock,andUpgradeToWriterLock/DowngradeFromWriterLock,correspondingly.Itisalsopossibletoprovideatimeoutvalue,afterwhichtherequesttoacquirethelockwillexpire.Providingthe-1valuemeansthatalockhasnotimeout.

NoteItisimportanttoalwaysreleasealockafteracquiringit.Alwaysputthecodeforreleasingalockintothefinallyblockofthetry/catchstatement,otherwiseanyexceptionthrownbeforereleasingthislockwouldleavetheReaderWriterLockobjectinalockedstate,preventinganyfurtheraccesstothislock.

Areaderlockputsathreadintheblockedstateonlywhenthereisatleastonewriterlockacquired.Otherwise,norealthreadblockinghappens.Awriterlockwaitsuntileveryotherlockisreleased,andtheninturnitpreventstheacquiringofanyotherlocks,untilit’sreleased.

Upgradingalockisuseful;wheninsideanopenreaderlock,weneedtowritesomethingintoacollection.Forexample,wefirstcheckifthereisanentrywithsomekeyinthe

www.EBooksWorld.ir

Page 57: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

dictionary,andinsertthisentryifitdoesnotexist.Acquiringawriterlockwouldbeinefficient,sincetherecouldbenowriteoperation,soitisoptimaltousethisupgradescenario.

Notethatusinganykindoflockisstillnotasefficientasasimplecheck,anditmakessensetousepatternssuchasdouble-checkedlocking.Considerthefollowcodesnippet:

if(writeRequiredCondition)

{

_rwLock.AcquireWriterLock();

try

{

if(writeRequiredCondition)

//dowrite

}

finally

{

_rwLock.ReleaseWriterLock();

}

}

TheReaderWriterLockclasshasanestedlockscounter,anditavoidscreatinganewlockwhentryingtoacquireitwheninsideanotherlock.Insuchacase,thelockcounterisincrementedandthendecrementedwhenthenestedlockisreleased.Thereallockisacquiredonlywhenthiscounterisequaltoto0.

Nevertheless,thisimplementationhassomeseriousdrawbacks.First,itusesthreadblocking,whichisquiteperformancecostly,andbesidesthat,addsitsownadditionaloverhead.Inaddition,ifthewriteoperationisveryshort,thenusingReaderWriterLockcouldbeevenworsethansimplylockingthecollectionforeveryoperation.Inadditiontothat,themethodnamesandsemanticsarenotintuitive,whichmakesreadingandunderstandingthecodemuchharder.

Thisisthereasonwhythenewimplementation,System.Threading.ReaderWriterLockSlim,wasintroducedin.NETFramework3.5.ItshouldalwaysbeusedinsteadofReaderWriterLockforthefollowingreasons:

Itismoreefficient,especiallywithshortlocks.Methodnamesbecamemoreintuitive:EnterReadLock/ExitReadLock,EnterWriteLock/ExitWriteLock,andEnterUpgradeableReadLock/ExitUpgradeableReadLock.Ifwetrytoacquireawriterlockinsideareaderlock,itwillbeanupgradebydefault.Insteadofusingatimeoutvalue,separatemethodshavebeenadded:TryEnterReadLock,TryEnterWriteLock,andTryEnterUpgradeableReadLock,whichmakethecodecleaner.Usingnestedlocksisnowforbiddenbydefault.Itispossibletoallownestedlocksbyspecifyingaconstructorparameter,butusingnestedlocksisusuallyamistakeandthisbehaviorhelpstoexplicitlydeclarehowitisintendedtodealwiththem.Internalenhancementshelptoimproveperformanceandavoiddeadlocks.

ThefollowingisanexampleofdifferentlockingstrategiesforDictionary<K,V>inthemultiplereaders/singlewriterscenario.First,wedefinehowmanyreadersandwriters

www.EBooksWorld.ir

Page 58: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

we’regoingtohave,howlongareadandwriteoperationwilltake,andhowmanytimestorepeatthoseoperations.

staticclassProgram

{

privateconstint_readersCount=5;

privateconstint_writersCount=1;

privateconstint_readPayload=100;

privateconstint_writePayload=100;

privateconstint_count=100000;

Thenwedefinethecommontestlogic.Thetargetdictionaryisbeingcreatedalongwiththereaderandwritermethods.ThemethodcalledMeasureusesLINQtomeasuretheperformanceofconcurrentaccess.

privatestaticreadonlyDictionary<int,string>_map=newDictionary<int,

string>();

privatestaticvoidReaderProc()

{

stringval;

_map.TryGetValue(Environment.TickCount%_count,outval);

//Dosomework

Thread.SpinWait(_readPayload);

}

privatestaticvoidWriterProc()

{

varn=Environment.TickCount%_count;

//Dosomework

Thread.SpinWait(_writePayload);

_map[n]=n.ToString();

}

privatestaticlongMeasure(Actionreader,Actionwriter)

{

varthreads=Enumerable

.Range(0,_readersCount)

.Select(n=>newThread(

()=>{

for(inti=0;i<_count;i++)

reader();

}))

.Concat(Enumerable

.Range(0,_writersCount)

.Select(n=>newThread(

()=>{

for(inti=0;i<_count;i++)

writer();

})))

.ToArray();

_map.Clear();

varsw=Stopwatch.StartNew();

foreach(varthreadinthreads)

thread.Start();

www.EBooksWorld.ir

Page 59: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

foreach(varthreadinthreads)

thread.Join();

sw.Stop();

returnsw.ElapsedMilliseconds;

}

Thenweusesimplelocktosynchronizeconcurrentaccesstothedictionary:

privatestaticreadonlyobject_simpleLockLock=newobject();

privatestaticvoidSimpleLockReader()

{

lock(_simpleLockLock)

ReaderProc();

}

privatestaticvoidSimpleLockWriter()

{

lock(_simpleLockLock)

WriterProc();

}

ThesecondtestisusinganolderReaderWriterLockclassasfollows:

privatestaticreadonlyReaderWriterLock_rwLock=new

ReaderWriterLock();

privatestaticvoidRWLockReader()

{

_rwLock.AcquireReaderLock(-1);

try

{

ReaderProc();

}

finally

{

_rwLock.ReleaseReaderLock();

}

}

privatestaticvoidRWLockWriter()

{

_rwLock.AcquireWriterLock(-1);

try

{

WriterProc();

}

finally

{

_rwLock.ReleaseWriterLock();

}

}

Finally,we’lldemonstratetheusageofReaderWriterLockSlim:

privatestaticreadonlyReaderWriterLockSlim_rwLockSlim=new

www.EBooksWorld.ir

Page 60: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ReaderWriterLockSlim();

privatestaticvoidRWLockSlimReader()

{

_rwLockSlim.EnterReadLock();

try

{

ReaderProc();

}

finally

{

_rwLockSlim.ExitReadLock();

}

}

privatestaticvoidRWLockSlimWriter()

{

_rwLockSlim.EnterWriteLock();

try

{

WriterProc();

}

finally

{

_rwLockSlim.ExitWriteLock();

}

}

Nowwerunallofthesetests,usingoneiterationasawarmuptoexcludeanyfirstrunissuesthatcouldaffecttheoverallperformance:

staticvoidMain()

{

//Warmup

Measure(SimpleLockReader,SimpleLockWriter);

//Measure

varsimpleLockTime=Measure(SimpleLockReader,SimpleLockWriter);

Console.WriteLine("Simplelock:{0}ms",simpleLockTime);

//Warmup

Measure(RWLockReader,RWLockWriter);

//Measure

varrwLockTime=Measure(RWLockReader,RWLockWriter);

Console.WriteLine("ReaderWriterLock:{0}ms",rwLockTime);

//Warmup

Measure(RWLockSlimReader,RWLockSlimWriter);

//Measure

varrwLockSlimTime=Measure(RWLockSlimReader,RWLockSlimWriter);

Console.WriteLine("ReaderWriterLockSlim:{0}ms",rwLockSlimTime);

}

}

www.EBooksWorld.ir

Page 61: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ExecutingthiscodeonCorei72600Kandx64OSintheReleaseconfigurationgivesthefollowingresults:

Simplelock:367ms

ReaderWriterLock:246ms

ReaderWriterLockSlim:183ms

ItshowsthatReaderWriterLockSlimisabout2timesfasterthantheusuallockstatement.

Youcanchangethenumberofreaderandwriterthreads,tweakthelocktime,andseehowtheperformancechangesineachcase.

NoteNotethatusingareaderwriterlockonthecollectionisnotenoughtoprovideapossibilitytoiterateoverthiscollection.Whilethecollectionitselfwillbeinthecorrectstate,whileiterating,ifanyofthecollectionitemswereremovedoradded,anexceptionwillbethrown.Thismeans,thatyouneedtoputalltheiterationprocessinsidealock,orproduceanewimmutablecopyofthecollectionanditerateoverthiscopy.

www.EBooksWorld.ir

Page 62: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 63: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SpinlockUsingoperatingsystemlevelsynchronizationprimitivesrequiresquiteanoticeableamountofresources,becauseofthecontextswitchingandalltheentirecorrespondingoverhead.Besidesthis,thereissuchthingaslocklatency;thatis,thetimerequiredforalocktobenotifiedaboutthestatechangeofanotherlock.Thismeansthatwhenthecurrentlockisbeingreleased,ittakessomeadditionaltimeforanotherlocktobesignaled.Thisisthereasonwhywhenweneedshorttimelocks,itcouldbesignificantlyfastertouseasinglethreadwithoutanylocksthantoparallelizetheseoperationsusingOSlevellockingmechanics.

Toavoidunnecessarycontextswitchesinsuchasituation,wecanusealoop,whichcheckstheotherlocksineachiteration.Sincethelocksshouldbeveryshort,wewouldnotusetoomuchCPU,andwehaveasignificantperformanceboostbynotusingtheoperatingsystemresourcesandbyloweringlocklatencytothelowestamount.

Thispatternisnotsoeasytoimplement,and,tobeeffective,youwouldneedtousespecificCPUinstructions.Fortunately,thereisastandardimplementationofthispatterninthe.NETFrameworkstartingwithversion3.5.Theimplementationcontainsthefollowingmethodsandclasses:

www.EBooksWorld.ir

Page 64: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thread.SpinWaitThread.SpinWaitjustspinsaninfiniteloop.It’slikeThread.Sleep,onlywithoutcontextswitchingandusingCPUtime.Itisusedrarelyincommonscenarios,butcouldbeusefulinsomespecificcases,suchassimulatingrealCPUwork.

www.EBooksWorld.ir

Page 65: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

System.Threading.SpinWaitSystem.Threading.SpinWaitisastructureimplementingaloopwithaconditioncheck.Itisusedinternallyinspinlockimplementation.

www.EBooksWorld.ir

Page 66: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

System.Threading.SpinLockHerewewillbediscussingaboutthespinlockimplementationitself.

NotethatitisastructurewhichallowstosaveonclassinstanceallocationandreducesGCoverhead.

Thespinlockcanoptionallyuseamemorybarrier(oramemoryfencinginstruction)tonotifyotherthreadsthatthelockhasbeenreleased.Thedefaultbehavioristouseamemorybarrier,whichpreventsmemoryaccessoperationreorderingbycompilerorhardware,andimprovesthefairnessofthelockattheexpenseofperformance.Theothercaseisfaster,butcouldleadtoincorrectbehaviorinsomesituations.

Usually,it’snotencouragedtouseaspinlockdirectlyunlessyouare100%surewhatyou’redoing.Makesurethatyouhaveconfirmedtheperformancebottleneckwithtestsandyouknowthatyourlocksarereallyshort.

Thecodeinsideaspinlockshouldnotdothefollowing:

Useregularlocks,oracodethatuseslocksAcquiremorethanonespinlockatatimePerformdynamicdispatchedcalls(virtualmethods,interfacemethods,ordelegatecalls)Callanythird-partycode,whichisnotcontrolledbyyouPerformmemoryallocation,includingnewoperatorusage

Thefollowingisasampletestforaspinlock:

staticclassProgram

{

privateconstint_count=10000000;

staticvoidMain()

{

//Warmup

varmap=newDictionary<double,double>();

varr=Math.Sin(0.01);

//lock

map.Clear();

varprm=0d;

varlockFlag=newobject();

varsw=Stopwatch.StartNew();

for(inti=0;i<_count;i++)

lock(lockFlag)

{

map.Add(prm,Math.Sin(prm));

prm+=0.01;

}

sw.Stop();

Console.WriteLine("Lock:{0}ms",sw.ElapsedMilliseconds);

//spinlockwithmemorybarrier

map.Clear();

varspinLock=newSpinLock();

www.EBooksWorld.ir

Page 67: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

prm=0;

sw=Stopwatch.StartNew();

for(inti=0;i<_count;i++)

{

vargotLock=false;

try

{

spinLock.Enter(refgotLock);

map.Add(prm,Math.Sin(prm));

prm+=0.01;

}

finally

{

if(gotLock)

spinLock.Exit(true);

}

}

sw.Stop();

Console.WriteLine("Spinlockwithmemorybarrier:{0}ms",

sw.ElapsedMilliseconds);

//spinlockwithoutmemorybarrier

map.Clear();

prm=0;

sw=Stopwatch.StartNew();

for(inti=0;i<_count;i++)

{

vargotLock=false;

try

{

spinLock.Enter(refgotLock);

map.Add(prm,Math.Sin(prm));

prm+=0.01;

}

finally

{

if(gotLock)

spinLock.Exit(false);

}

}

sw.Stop();

Console.WriteLine("Spinlockwithoutmemorybarrier:{0}ms",

sw.ElapsedMilliseconds);

}

}

ExecutingthiscodeonCorei72600Kandx64OSinReleaseconfigurationgivesthefollowingresults:

Lock:1906ms

Spinlockwithmemorybarrier:1761ms

Spinlockwithoutmemorybarrier:1731ms

Notethattheperformanceboostisverysmallevenwithshortdurationlocks.Alsonotethatstartingfrom.NETFramework3.5,theMonitor,ReaderWriterLock,andReaderWriterLockSlimclassesareimplementedwithspinlock.

www.EBooksWorld.ir

Page 68: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

NoteThemaindisadvantageofspinlocksisintensiveCPUusage.Theendlessloopconsumesenergy,whiletheblockedthreaddoesnot.However,nowthestandardMonitorclasscanusespinlockforashorttimelockandthenturntousuallock,soinrealworldscenariosthedifferencewouldbeevenlessnoticeablethaninthistest.

www.EBooksWorld.ir

Page 69: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 70: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

OptimizationstrategyCreatingparallelalgorithmsisnotasimpletask:thereisnouniversalsolutiontoit.Ineverycase,youhavetouseaspecificapproachtowriteeffectivecode.However,thereareseveralsimplerulesthatworkformostoftheparallelprograms.

www.EBooksWorld.ir

Page 71: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

LocklocalizationThefirstthingtotakeintoaccountwhenwritingparallelcodeistolockaslittlecodeaspossible,andensurethatthecodeinsidethelockrunsasfastaspossible.Thismakesitlessdeadlock-proneandscalebetterwiththenumberofCPUcores.Tosumup,acquirethelockaslateaspossibleandreleaseitassoonaspossible.

Letusconsiderthefollowingsituation:forexample,wehavesomecalculationperformedbymethodCalcwithoutanysideeffects.Wewouldliketocallitwithseveraldifferentargumentsandstoretheresultsinalist.Thefirstintentionistowritethecodeasfollows:

for(vari=from;i<from+count;i++)

lock(_result)

_result.Add(Calc(i));

Thiscodeworks,butwecalltheCalcmethodandperformthecalculationinsideourlock.Thiscalculationdoesnothaveanysideeffects,andthusrequiresnolocking,soitwouldbemuchmoreefficienttorewritethecodeasshownnext:

for(vari=from;i<from+count;i++)

{

varcalc=Calc(i);

lock(_result)

_result.Add(calc);

}

Ifthecalculationtakesasignificantamountoftime,thenthisimprovementcouldmakethecoderunseveraltimesfaster.

www.EBooksWorld.ir

Page 72: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ShareddataminimizationAnotherwayofimprovingparallelcodeperformanceisbyminimizingtheshareddata,whichisbeingwritteninparallel.Itisacommonsituationwhenwelockoverthewholecollectioneverytimewewriteintoit,insteadofthinkingandloweringtheamountoflocksandthedatabeinglocked.Organizingconcurrentaccessanddatastorageinawaythatitminimizesthenumberoflockscanleadtoasignificantperformanceincrease.

Inthepreviousexample,welockedtheentirecollectioneachtime,asdescribedinthepreviousparagraph.However,wereallydon’tcareaboutwhichworkerthreadprocessesexactlywhatpieceofinformation,sowecouldrewritethepreviouscodelikethefollowing:

vartempRes=newList<string>(count);

for(vari=from;i<from+count;i++)

{

varcalc=Calc(i);

tempRes.Add(calc);

}

lock(_result)

_result.AddRange(tempRes);

Thefollowingisthecompletecomparison:

staticclassProgram

{

privateconstint_count=1000000;

privateconstint_threadCount=8;

privatestaticreadonlyList<string>_result=newList<string>();

privatestaticstringCalc(intprm)

{

Thread.SpinWait(100);

returnprm.ToString();

}

privatestaticvoidSimpleLock(intfrom,intcount)

{

for(vari=from;i<from+count;i++)

lock(_result)

_result.Add(Calc(i));

}

privatestaticvoidMinimizedLock(intfrom,intcount)

{

for(vari=from;i<from+count;i++)

{

varcalc=Calc(i);

lock(_result)

_result.Add(calc);

}

}

privatestaticvoidMinimizedSharedData(intfrom,intcount)

www.EBooksWorld.ir

Page 73: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

vartempRes=newList<string>(count);

for(vari=from;i<from+count;i++)

{

varcalc=Calc(i);

tempRes.Add(calc);

}

lock(_result)

_result.AddRange(tempRes);

}

privatestaticlongMeasure(Func<int,ThreadStart>actionCreator)

{

_result.Clear();

varthreads=

Enumerable

.Range(0,_threadCount)

.Select(n=>newThread(actionCreator(n)))

.ToArray();

varsw=Stopwatch.StartNew();

foreach(varthreadinthreads)

thread.Start();

foreach(varthreadinthreads)

thread.Join();

sw.Stop();

returnsw.ElapsedMilliseconds;

}

staticvoidMain()

{

//Warmup

SimpleLock(1,1);

MinimizedLock(1,1);

MinimizedSharedData(1,1);

constintpart=_count/_threadCount;

vartime=Measure(n=>()=>SimpleLock(n*part,part));

Console.WriteLine("Simplelock:{0}ms",time);

time=Measure(n=>()=>MinimizedLock(n*part,part));

Console.WriteLine("Minimizedlock:{0}ms",time);

time=Measure(n=>()=>MinimizedSharedData(n*part,part));

Console.WriteLine("Minimizedshareddata:{0}ms",time);

}

}

ExecutingthiscodeonCorei72600Kandx64OSinReleaseconfigurationgivesthefollowingresults:

Simplelock:806ms

Minimizedlock:321ms

Minimizedshareddata:165ms

www.EBooksWorld.ir

Page 74: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 75: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,welearnedabouttheissueswithusingshareddatafrommultiplethreads.Welookedthroughthedifferenttechniquesallowingustoorganizeconcurrentaccesstosharedstatemoreefficientlyindifferentscenarios.Wealsoestablishedanunderstandingabouttheperformanceissuesofusinglocks,threadblocking,andcontextswitching.

Inthenextchapter,wewillcontinuetoexploreconcurrentaccesstoshareddata.However,thistimewewilltrytoavoidlocksandmakeourparallelprogrammorerobustandefficient.

www.EBooksWorld.ir

Page 76: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 77: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter2.Lock-FreeConcurrencyInChapter1,TraditionalConcurrency,wereviewedthreadsynchronizationwithlockingandhowtouselockseffectively.However,therewillbestillperformanceoverheadrelatedtolocking.Thebestwaytoavoidsuchissuesisbynotusinglocksatallwheneverpossible.Algorithmsthatdonotuselockingarereferredtoaslock-freealgorithms.

Lock-freealgorithmsinturnareofdifferenttypes.Oneofthemostimportanttypesiswait-freealgorithms.Thesealgorithmsnotonlyevadetheuseoflocks,butalsoareguaranteedtonotwaitforanyeventsfromotherthreads.Thisisabest-casescenariobutunfortunately,itisararesituationwhenwecanavoidwaitingfortheotherthreadsatall.Usually,arealconcurrentprogramtriestobeascloseaspossibletowait-free,andthisiswhateverydevelopershouldtrytoachieve.

ThereisonemorecategoryofalgorithmsthatdonotuseOS-levelthreadblockingbutusespinlocks.Thisallowsthecreationofquiteefficientcodeinsituationswhenthecodeinsidethelockhastorunveryfast.Suchalgorithmscanbecalledlock-freeinvarioussources,butstrictlyspeakingtheyarenotastheydonotguaranteethatthealgorithmwillbeprogressing,sinceitispossibleitgetsblockedinvarioussituations.WewilldiscusssuchsituationslaterinChapter10,TroubleshootingParallelPrograms.

NotePleasenoticethatamultithreadedprogramcanbetargetedindifferentscenarios,andthusthemetricscouldbedifferent.Forexample,ifourgoalistosavethebatterychargeofalaptoportosavetheCPUworkload,lockingtechniquesarepreferred(untilsomepointwhentherewillbetoomanyblockedthreads).However,ifweneedoverallperformance,thenlock-freealgorithmsareusuallybetter.

www.EBooksWorld.ir

Page 78: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

MemorymodelandcompileroptimizationsMemorymodelandcompileroptimizationsarenotdirectlyrelatedtoconcurrency,buttheyareveryimportantconceptsforanyonewhocreatesconcurrentcode,shownasfollows:

classProgram

{

bool_loop=true;

staticvoidMain(string[]args)

{

varp=newProgram();

Task.Run(()=>

{

Thread.Sleep(100);

p._loop=false;

});

while(p._loop);

//while(p._loop){Console.Write(".");};

Console.WriteLine("Exitedtheloop");

}

}

IfyoucompilethiswiththeReleasebuildconfigurationandJITcompileroptimizationsenabled,theloopwillusuallyhangonthex86andx64architectures.ThishappensbecauseJIToptimizesthep._loopreadanddoessomethinglikethis:

if(p._loop)

{

while(true);

}

Ifthereissomethinginsidethewhileloop,JITwillprobablynotoptimizethiscodeinthisway.Also,wemayusethevolatilekeywordwiththeBooleanflaglikethis:

volatilebool_loop;

Inthiscase,JITwillturnoffthisoptimizationaswell.Thisiswhereweuseamemorymodel,anditgetscomplicatedhere.HereisaquotefromtheC#languagespecification:

Fornon-volatilefields,optimizationtechniquesthatreorderinstructionscanleadtounexpectedandunpredictableresultsinmulti-threadedprogramsthataccessfieldswithoutsynchronizationsuchasthatprovidedbythelock-statement.Theseoptimizationscanbeperformedbythecompiler,bytherun-timesystem,orbyhardware.Forvolatilefields,suchreorderingoptimizationsarerestricted:

Areadofavolatilefieldiscalledavolatileread.Avolatilereadhas“acquiresemantics”;thatis,itisguaranteedtooccurpriortoanyreferencestomemorythatoccurafteritintheinstructionsequence.

www.EBooksWorld.ir

Page 79: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Awriteofavolatilefieldiscalledavolatilewrite.Avolatilewritehas“releasesemantics”;thatis,itisguaranteedtohappenafteranymemoryreferencespriortothewriteinstructionintheinstructionsequence.

Aswecansee,thereisnothingspecificallystatedhereaboutcompileroptimizations,butinfactJITdoesnotoptimizevolatilefieldreadinthiscase.

Sowecanseeadescriptioninaspecification,buthowdoesthisreallywork?Let’slookatavolatilereadexample:

classVolatileRead

{

int_x;

volatileint_y;

int_z;

voidRead()

{

intx=_x;//1

inty=_y;//2(volatile)

intz=_z;//3

}

}

Thepossiblereorderingoptionswouldbe1,2,3(original);2,1,3;and2,3,1.Thiscanbeimaginedasaone-wayfencethatallowstheprecedingoperationtopassthrough,butdoesnotallowsubsequentoperations.Sothisiscalledtheacquirefence.

Volatilewriteslookprettysimilar.Considerthefollowingcodesnippet:

classVolatileWrite

{

int_x;

volatileint_y;

int_z;

voidRead()

{

_x=1;//1

_y=2;//2(volatile)

_z=3;//3

}

}

Possibleoptionshereare1,2,3(original);1,3,2;and3,1,2.Thisisthereleasefence,whichallowsthereorderingofonlysubsequentreadorwriteoperationsbutdoesnotallowtheprecedingwriteoperation.WehavetheThread.VolatileReadandThread.VolatileWritemethodsthatdothesamethingexplicitly.ThereistheThread.MemoryBarrier(memorybarrier)methodaswell,whichallowsustouseafullfencewhenwedonotletthroughanyoperations.

Iwouldliketomentionthatwearenowonlesscertainground.Differentmemorymodelsondifferentarchitecturescanbeconfusing,andcodewithoutvolatilecanperfectlyworkonx86andamd64.However,ifyouareusingshareddata,pleasebeawareofpossible

www.EBooksWorld.ir

Page 80: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

reorderingandnon-reorderingoptimizationsandchoosetheappropriatebehavior.

NotePleasebeawarethatmakingafieldvolatilemeansthatallthereadandwriteoperationswillhaveslightlylowerperformanceandtheywillhavethecodeincommon,sincesomepossibleoptimizationswillbeignored.

www.EBooksWorld.ir

Page 81: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 82: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TheSystem.Threading.InterlockedclassWhenwereviewedraceconditionsinthepreviouschapter,welearnedthatevenasimpleincrementoperationconsistsofthreeseparateactions.AlthoughmodernCPUscanperformsuchoperationsatonce,itisnecessarytomakethemsafetobeusedinconcurrentprograms.

The.NETFrameworkcontainstheSystem.Threading.Interlockedclassthatprovidesaccesstoseveraloperationsthatareatomic,whichmeansthattheyareuninterruptibleandappeartooccurinstantaneouslytotherestofthesystem.Thesearetheoperationsthatthelock-freealgorithmsarebasedon.

Let’srevisearaceconditionexampleandcomparethelockingandInterlockedclassoperations.First,wewillusethetraditionallockingapproach:

varcounterLock=newobject();

varcounter=0;

ThreadStartproc=

()=>

{

for(inti=0;i<count;i++)

{

lock(counterLock)

counter++;

Thread.SpinWait(100);

lock(counterLock)

counter--;

}

};

varthreads=

Enumerable

.Range(0,8)

.Select(n=>newThread(proc))

.ToArray();

varsw=Stopwatch.StartNew();

foreach(varthreadinthreads)

thread.Start();

foreach(varthreadinthreads)

thread.Join();

sw.Stop();

Console.WriteLine("Locks:counter={0},time={1}ms",counter,

sw.ElapsedMilliseconds);

Now,let’sreplacelockingwiththeInterlockedclassmethodcalls:

counter=0;

ThreadStartproc2=

()=>

{

for(inti=0;i<count;i++)

{

Interlocked.Increment(refcounter);

Thread.SpinWait(100);

Interlocked.Decrement(refcounter);

www.EBooksWorld.ir

Page 83: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

}

};

threads=

Enumerable

.Range(0,8)

.Select(n=>newThread(proc2))

.ToArray();

sw=Stopwatch.StartNew();

foreach(varthreadinthreads)

thread.Start();

foreach(varthreadinthreads)

thread.Join();

sw.Stop();

Console.WriteLine("Lockfree:counter={0},time={1}ms",counter,

sw.ElapsedMilliseconds);

Asaresult,wegotthisonareferencecomputer:

Locks:counter=0,time=1892ms

Locks:counter=0,time=800ms

Justusingatomicoperationsperformedmorethantwiceaswellandkepttheprogramlogiccorrect.

Anothertrickypartis64-bitintegercalculations.Whentheprogramrunsinthe64-bitmode,thereadandwriteoperationsfor64-bitintegernumbersareatomic.However,whenrunninginthe32-bitmode,theseoperationsbecomenonatomicandconsistoftwoparts—reading/writinghigh32bitsandlow32bitsofthenumber.

TheInterlockedclasscontainstheReadmethodthatcanreada64-bitintegerinthe32-bitmodeasanatomicoperation.Thisisnotrequiredin64-bitmode,butifyoucompileyourprograminanyCPUmodethenyoushouldusethismethodtoguaranteeatomicityofreads.TherearetheIncrementandDecrementmethodoverloadsfor64-bitintegersaswell,andthereistheAddmethodthatallowsustohaveatomicadditionof32-bitand64-bitintegers.

Anotherveryimportantoperationisthevalueexchange.Lookingatthefollowingcodeitisobviousthatthisoperationisnotatomic,andthuswemustputthiscodeinsidesomekindoflocktokeepthisoperationcorrectinaconcurrentprogram:

vartmp=a;

a=b;

b=tmp;

TheInterlockedclassallowsustoperformthisoperationasatomicwiththeExchangemethod:

b=Interlocked.Exchange(refa,b)

Thereareseveraloverloadsforthismethodthatallowustoexchangethenumericvaluesofdifferenttypesincluding32-bitand64-bitintegers,thefloatanddoublevalues,objectreferences(thereisagenericversionofthismethodwiththetypeparameter),andtheIntPtrstructures.

ThemostcomplicatedatomicoperationprovidedbytheInterlockedclassisthe

www.EBooksWorld.ir

Page 84: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CompareExchangemethod.Itacceptsthreearguments,thenitcomparesthefirstargumentwiththethird;iftheyareequal,itassignsthesecondargumentvaluetothefirstargument.Thisisperformedbyspecialinstructiononhardwaretoo.Wewillseeanexampleofthislaterinthischapterwhenwetrytoimplementalock-freequeue.

NoteAlltheInterlockedclassmethodcallsimplicitlygeneratefullfences.

www.EBooksWorld.ir

Page 85: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 86: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

InterlockedinternalsTounderstandhowinterlockedinternalsworkunderthehood,we’regoingtoseewhatmachinecodeisbeinggeneratedwhencompilingtheInterlocked.Incrementmethod.Ifwejustruntheprogramindebugmodeandlookatthedisassemblywindow,wewillseetheusualmethodcall.

Toseewhatisreallygoingon,wehavetoenablealloptimizations:

1. First,weneedtobuildthecodeintheReleasemodeinVisualStudio.2. Then,wehavetogotoTools|Options|Debugging|Generalanduncheckthe

SuppressJIToptimizationonmoduleloadoption.3. Finally,addaSystem.Diagnostics.Debugger.Break()methodcalltopausethe

codeindebugger.

Ifeverythingisset,youwillseethefollowingcodeinthedisassemblywindow:

Interlocked.Increment(refcounter);

00007FFEF22B49AElearcx,[rsi+20h]

00007FFEF22B49B2lockadddwordptr[rcx],1

NotePleasenoticethelockprefixinthelastlineofthecode.ThisprefixisaninstructiontotheCPUtoperformanatomicincrementoperation.ThismeansthattheInterlockedclassisnotausualclass,butahinttotheJITcompilertogenerateaspecialcode.

www.EBooksWorld.ir

Page 87: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 88: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Writinglock-freecodeSincewehaveaverylimitednumberofatomicoperations,itisveryhardtowritelock-freecode.Forsomecommondatastructures,suchasadoublelinkedlist,thereisnolock-freeimplementation.Besides,itisveryeasytomakeamistake,andthemainproblemisthatsuchcodecouldworkfine99.9percentofthetime,whichmakesdebuggingenormouslyconfusing.

Therefore,thebestpracticeistousestandardimplementationsofsuchalgorithms.AgoodplacetostartisbyusingconcurrentcollectionsfromtheSystem.Collections.Concurrentnamespacethatwasintroducedinthe.NETFramework4.0.WewillreviewthemindetailinChapter6,UsingConcurrentDataStructures.However,nowwewilltrytodonotasadvisedandimplementalock-freestackandalock-freequeuefromscratch.

Thecornerstoneofthelock-freecodeisthefollowingpattern:readsomedatafromthesharedstate,calculateanewvalue,andthenwritethenewvalueback,butonlyifthesharedstatewasn’tmutatedbyanyotherthreadbythattime.Thelastcheckandwriteoperationmustbeatomic,andthisiswhatweuseInterlocked.CompareExchangefor.Thisdescriptionlooksabitconfusing,butitcanbeillustratedwithquiteaneasyexample.Imaginemultiplethreadscalculatinganintegersuminparallel.Considerthefollowinglineofcode,forexample:

_total+=current;

Ifweusethissimplecode,wewouldgetraceconditionheresincethisoperationisnotatomic.TheeasiestwaytofixthisisbyusingatomicadditionwiththeInterlocked.Addmethod,buttoillustratetheCompareExchangemethodlogic,let’simplementtheadditionlikethis:

intbeforeValue,newValue;

do

{

beforeValue=_total;

newValue=beforeValue+current;

}

while(beforeValue!=Interlocked.CompareExchange(ref_total,newValue,

beforeValue))

First,wesavethe_totalvalueinthebeforeValuetemporaryvariable.Then,wecalculateanewvalueandstoreitinnewValue.Finally,we’retryingtosavenewValuein_total,butonlyif_totalremainsthesamewhenwestartedtheoperation.Ifnot,itmeansthatthe_totalvaluehasbeenchangedbyanotherthreadandwehavetorepeattheoperationwiththenewvalueof_total.

www.EBooksWorld.ir

Page 89: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TheABAproblemRememberwhenwementionedthatlock-freeprogrammingisverycomplicated?Now,it’stimetoproveit.Hereisanothercasewhenaseeminglyrightconcurrentcodeworksabsolutelywrong.

Imaginethatwehavealock-freestackimplementationwiththeInterlocked.CompareExchangeatomiccompare-and-swap(CAS)operation.Let’sassumethatitcontainsthreeitems:Aontop,B,andC.Thread1callsthePopmethod;itsetstheoldheadvalueasAandthenewheadvalueasB.Howeverforsomereason,thread1getssuspendedbytheoperatingsystem.Meanwhile,thread2popsitemAfromthestackandsavesitforlateruse.Then,itpushesitemDonthestack.Afterdoingthis,itfinallypushesitemAbackontopofthestack,butthistimeA’snextitemisDandourstackcontainsfouritems:Aontop,D,B,andC.

Nowthefirstthreadcontinuestorun.Itcompareswhethertheoldheadvalueandthecurrentheadvaluearethesame,andtheyare!Therefore,thethreadwritesvalueBtotheheadofthestack.Now,thestackiscorruptedandcontainstwoitems:BonthetopandC.

Thedescribedprocesscanbeillustratedbythefollowingschema:

www.EBooksWorld.ir

Page 90: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

So,justhavingatomicCASoperationsisnotenough.Tomakethiscodeworkright,it’sveryimportanttomakesurethatwedonotreusereferencesinourcodeorallowthemtoescapetoourconsumers.Thus,whenwepushitemAtwice,itshouldbedifferentfromtheexistingitemsfromthestackperspective.Toachievethis,it’senoughtoallocateanewwrapperobjecteachtimesomethingisbeingpushedontothestack.

HereisaquotefromWikipediathatdescribestheABAproblemverywell:

Natalieiswaitinginhercarataredtrafficlightwithherchildren.Herchildrenstartfightingwitheachotherwhilewaiting,andsheleansbacktoscoldthem.Oncetheirfightingstops,Nataliechecksthelightagainandnoticesthatit’sstillred.However,whileshewasfocusingonherchildren,thelighthadchangedtogreen,andthenbackagain.Nataliedoesn’tthinkthelighteverchanged,butthepeoplewaitingbehindherareverymadandhonkingtheirhornsnow.

www.EBooksWorld.ir

Page 91: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thelock-freestackNow,wearereadytoimplementalock-freestackdatastructure.First,wedefineabaseabstractclassforourstackimplementation:

publicabstractclassStackBase<T>

Thenwehaveaninnerclasstodefineanitemonthestack:

privateclassItem

{

privatereadonlyT_data;

privatereadonlyItem_next;

publicItem(Tdata,Itemnext)

{

_data=data;

_next=next;

}

publicTData

{

get{return_data;}

}

publicItemNext

{

get{return_next;}

}

}

Theitemclasscontainsuserdataandareferencetothenextelementonthestack.Now,we’readdingastacktopitem:

privateItem_head;

Apropertythatindicateswhetherthestackisemptyisasfollows:

publicboolIsEmpty

{

get{return_head==null;}

}

Twoabstractmethodsthatstoreandretrieveanitemfromthestack:

publicabstractvoidPush(Tdata);

publicabstractboolTryPop(outTdata);

Nowwehaveabasefordifferentstackimplementationstocomparehowtheyperform.Westartwithalock-basedstack:

publicclassLockStack<T>:StackBase<T>

Asweremember,thelockstatementistranslatedtotheMonitorclassmethodcallsbytheC#compiler.ThemonitorclasstriestoavoidusingOS-levellocksandusesspinlockstoachieveaperformanceboostwhenalocktakesalittletime.We’regoingtoillustratethis

www.EBooksWorld.ir

Page 92: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

andcreateastackthatusesonlyOS-levellockswiththehelpoftheSystem.Threading.Mutexclass,whichusesthemutexsynchronizationprimitivefromtheOS.Wecreateamutexinstance:

privatereadonlyMutex_lock=newMutex();

Then,implementthePushandPopmethodsasfollows:

publicoverridevoidPush(Tdata)

{

_lock.WaitOne();

try

{

_head=newItem(data,_head);

}

finally

{

_lock.ReleaseMutex();

}

}

publicoverrideboolTryPop(outTdata)

{

_lock.WaitOne();

try

{

if(IsEmpty)

{

data=null;

returnfalse;

}

data=_head.Data;

_head=_head.Next;

returntrue;

}

finally

{

_lock.ReleaseMutex();

}

}

Thisimplementationputsathreadinablockedstateeverytimeithastowaitforthelocktobereleased.Thisistheworst-casescenario,andwe’regoingtoseethetestresultsthatprovethis.

Nowwewillimplementaconcurrentstackwithamonitorandlockstatement:

publicclassMonitorStack<T>:StackBase<T>whereT:class

{

privatereadonlyobject_lock=newobject();

publicoverridevoidPush(Tdata)

{

lock(_lock)

_head=newItem(data,_head);

}

www.EBooksWorld.ir

Page 93: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

publicoverrideboolTryPop(outTdata)

{

lock(_lock)

{

if(IsEmpty)

{

data=null;

returnfalse;

}

data=_head.Data;

_head=_head.Next;

returntrue;

}

}

}

Thenit’sthelock-freestackimplementation’sturn:

publicclassLockFreeStack<T>whereT:class

Noticethatwehadtoaddclassconstrainttothegenerictypeparameter.Wedothisbecausewecannotatomicallyexchangevaluesthataremorethan8bytesinsize.IfwelookatthegenericversionoftheInterlocked.CompareExchangemethod,wecanmakesurethatitstypeparameterhasthesameclassconstraint.

Let’sgettoimplementation:

publicvoidPush(Tdata)

{

Itemitem,oldHead;

do

{

oldHead=_head;

item=newItem(data,oldHead);

}while(oldHead!=Interlocked.CompareExchange(ref_head,item,

oldHead));

}

Thisimplementationisquitesimilartoalock-freeadditionexample.Webasicallydothesamething,onlyinsteadofaddition,we’restoringanewreferencetothestack’shead.

TheTryPopmethodcodeisslightlymorecomplicated:

publicboolTryPop(outTdata)

{

varoldHead=_head;

while(!IsEmpty)

{

if(oldHead==Interlocked.CompareExchange(ref_head,oldHead.Next,

oldHead))

{

data=oldHead.Data;

returntrue;

}

oldHead=_head;

}

www.EBooksWorld.ir

Page 94: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

data=null;

returnfalse;

}

Herewehavetonoticethatthestackcanbeempty;inthiscase,wereturnfalsetoindicatethatwefailedtoretrieveavaluefromthestack.

Also,wewouldliketocompareourcodetothestandardConcurrentStackimplementationfromSystem.Collections.Concurrent.Itispossibletouseaninterfacetoworkwithallcollectionsinthesameway,butinthiscase,itiseasiertocreateawrapperclassthatcontainsthesourcecollection:

publicclassConcurrentStackWrapper<T>:StackBase<T>

{

privatereadonlyConcurrentStack<T>_stack;

publicConcurrentStackWrapper()

{

_stack=newConcurrentStack<T>();

}

publicoverridevoidPush(Tdata)

{

_stack.Push(data);

}

publicoverrideboolTryPop(outTdata)

{

return_stack.TryPop(outdata);

}

}

Theonlyoperationleftistocomparetheperformancesofourstackimplementations:

privatestaticlongMeasure(StackBase<string>stack)

{

varthreads=Enumerable

.Range(0,_threadCount)

.Select(

n=>newThread(

()=>

{

for(varj=0;j<_iterations;j++)

{

for(vari=0;i<_iterationDepth;i++)

{

stack.Push(i.ToString());

}

stringres;

for(vari=0;i<_iterationDepth;i++)

{

stack.TryPop(outres);

}

}

}))

www.EBooksWorld.ir

Page 95: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

.ToArray();

varsw=Stopwatch.StartNew();

foreach(varthreadinthreads)

thread.Start();

foreach(varthreadinthreads)

thread.Join();

sw.Stop();

if(!stack.IsEmpty)

thrownewApplicationException("Stackmustbeempty!");

returnsw.ElapsedMilliseconds;

}

Werunseveralthreadsandeachofthesethreadspushesandpopsitemstothestackinparallel.Wewaitforallthethreadstocomplete,andcheckwhetherthestackisempty,whichmeansthattheprogramiscorrect.Finally,wemeasurethetimerequiredforalltheoperationstocomplete.

TheresultscanbedifferentandgreatlydependontheCPU.Thisoneisfroma3.4GHzquadcoreInteli7-3770CPU:

LockStack:6718ms

LockFreeStack:209ms

MonitorStack:154ms

ConcurrentStack:121ms

Thisoneisfromahyper-vvirtualmachinewithtwoCPUcoresrunningona2.2GHzquadcoreInteli7-4702HQCPUlaptopwithpowersavingmodeenabled:

LockStack:39497ms

LockFreeStack:388ms

MonitorStack:691ms

ConcurrentStack:419ms

Thetypicalresultsareasfollows:LockStackistheslowest,theLockFreeStackandMonitorStakimplementationsperformaboutthesame,andthestandardConcurrentStackshowsthebestresults.TheMonitorStackimplementationworkswellbecause,inthiscase,operationsunderlockareveryfast,thatis,abouttwoprocessorcycles,andinthissituation,spinwaitworksverywell.We’llgetbacktoexplainingtheseresultsindetaillaterinChapter6,UsingConcurrentDataStructures.

www.EBooksWorld.ir

Page 96: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thelock-freequeueStackandqueuearethesimplestofbasicdatastructures.Wehaveimplementedalock-freestack,andweencounteredseveraltrickyproblemsthatwehadtoresolve.Implementingalock-freeconcurrentqueueisamoresophisticatedtask,sincewenowhavetoperformseveraloperationsatonce.Forexample,whenwequeueanewitem,wemustsimultaneouslysettheoldtail’snextitemreferencetoanewitemandthenchangeatailreferencethatthenewitemisnowanewtail.Unfortunately,wecannotchangetwoobjectsasanatomicoperation.So,wemustfindawaytoproperlysynchronizeaccesstotheheadandtailwithoutlocks:

publicclassLockFreeQueue<T>

{

Wedefineasimpleclassthatisgoingtocontaindatainthequeue:

protectedclassItem

{

publicTData;

publicItemNext;

}

Wewillstorereferencestothequeue’stailandheadandinitializethembydefault:

privateItem_head;

privateItem_tail;

publicLockFreeQueue()

{

_head=newItem();

_tail=_head;

}

ThefirstchallengeistoimplementanEnqueuemethod.Whatwecandoissettail.NextintheCASoperation,butletthetailreferenceadvancelater,maybebyotherthreads.Thisguaranteesthatthelinkedlistofqueueitemswillalwaysbevalid,andifweseethatwefailedtosetanewtail,justletthisoperationstartinanotherthread:

publicvoidEnqueue(Tdata)

{

Createanewqueueitemandreservespaceforthelocalcopiesofthe_tailand_tail.Nextreferences:

Itemitem=newItem();

item.Data=data;

ItemoldTail=null;

ItemoldNext=null;

Werepeatthequeueingoperationuntilitsucceeds:

boolupdate=false;

while(!update){

Copyreferencestolocalvariablesandacquireafullfencesothatthereadandwritewww.EBooksWorld.ir

Page 97: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

operationswillnotbereordered.WehavetousetheNextfieldfromthelocalcopy,becausetheactual_tailitemmayhavealreadybeenchangedbetweenboththereadoperations:

oldTail=_tail;

oldNext=oldTail.Next;

Thread.MemoryBarrier();

Thetailmayremainthesameasitwasinthebeginningoftheoperation:

if(_tail==oldTail)

{

Inthiscase,thenextreferencewasnull,whichmeansthatnoonechangedthetailsincewecopiedittooldNext:

if(oldNext==null)

{

Herewecantryqueueinganitem,andthiswillbethesuccessofthewholeoperation:

update=Interlocked.CompareExchange(ref_tail.Next,item,null)==null;

}

else

{

Ifnot,itmeansthatanotherthreadisqueueinganewitemrightnow,soweshouldtrytosetthetailreferencetopointtoitsnextnode:

Interlocked.CompareExchange(ref_tail,oldNext,oldTail);

}

}

}

Herewehavesuccessfullyinsertedanewitemtotheendofthequeue,andnowwe’retryingtoupdatethetailreference.However,ifwefailitisokay,sinceanotherthreadwilleventuallydothisinitsEnqueuemethodcall:

Interlocked.CompareExchange(ref_tail,item,oldTail);

}

Themaingoalofdequeueingproperlyistocorrectlyworkinsituationswhenwehavenotyetupdatedthetailreference:

publicboolTryDequeue(outTresult)

{

Wewillcreatealoopthatfinishesifthereisnothingtodequeueorifwehavedequeuedanitemsuccessfully:

result=default(T);

ItemoldNext=null;

booladvanced=false;

while(!advanced)

{

www.EBooksWorld.ir

Page 98: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Wewillmakelocalcopiesofvariablesthatareneeded:

ItemoldHead=_head;

ItemoldTail=_tail;

oldNext=oldHead.Next;

Then,wewillacquireafullfencetopreventreadandwritereordering:

Thread.MemoryBarrier();

Theremightbeacasewhentheheaditemhasnotbeenchangedyet:

if(oldHead==_head)

{

Then,wewillcheckwhetherthequeueisempty:

if(oldHead==oldTail)

{

Inthiscase,thisshouldbefalse.Ifnot,itmeansthatwehavealaggingtailandweneedtoupdateit:

if(oldNext!=null)

{

Interlocked.CompareExchange(ref_tail,oldNext,oldTail);

continue;

}

Ifwearehere,wehaveanemptyqueue:

result=default(T);

returnfalse;

}

Nowwewillgetthedequeueingitemandtrytoadvancetheheadreference:

result=oldNext.Data;

advanced=Interlocked.CompareExchange(

ref_head,oldNext,oldHead)==oldHead;

}

}

Wewillremoveanyreferencesthatcanpreventthegarbagecollectorfromdoingitsjob,andthenwewillexit:

oldNext.Data=default(T);

returntrue;

}

publicboolIsEmpty

{

get

{

return_head==_tail;

}

}

www.EBooksWorld.ir

Page 99: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

}

Thenwewillwritethefollowingcodetounifyaccesstoqueuesandcomparedifferentwaystosynchronizeaccesstothequeue.Towritegeneralperformancemeasurementcode,weneedtowriteaninterface:

publicinterfaceIConcurrentQueue<T>

{

voidEnqueue(Tdata);

boolTryDequeue(outTdata);

boolIsEmpty{get;}

}

BothLockFreeQueueandthestandardConcurrentQueuearealreadyimplementingthisinterface,andallweneedtodoistocreateawrapperclasslikethis:

classLockFreeQueueWrapper<T>:LockFreeQueue<T>,IConcurrentQueue<T>{}

classConcurrentQueueWrapper<T>:ConcurrentQueue<T>,IConcurrentQueue<T>

{}

Weneedamoreadvancedwrapperinthecaseofanon-thread-safeQueuecollection:

classQueueWrapper<T>:IConcurrentQueue<T>

{

privatereadonlyobject_syncRoot=newobject();

privatereadonlyQueue<T>_queue=newQueue<T>();

publicvoidEnqueue(Tdata)

{

lock(_syncRoot)

_queue.Enqueue(data);

}

publicboolTryDequeue(outTdata)

{

if(_queue.Count>0)

{

lock(_syncRoot)

{

if(_queue.Count>0)

{

data=_queue.Dequeue();

returntrue;

}

}

}

data=default(T);

returnfalse;

}

publicboolIsEmpty

{

get{return_queue.Count==0;}

}

}

www.EBooksWorld.ir

Page 100: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

WehaveusedadoublecheckedlockingpatterninsidetheTryDequeuemethod.Atfirstglance,itseemsthatthefirstifstatementisnotdoinganythinguseful,andwecanjustremoveit.Ifyoudoanexperimentandruntheprogramwithoutthefirstcheck,itwillrunabout50timesslower.Thegoalofthefirstcheckistoseewhetherthequeueisemptysothatalockisnotacquired;thelockandotherthreadsareallowedtoaccessthequeue.Makingalockcodeminimalisveryimportant,anditisillustratedhereverywell.

Nowweneedperformancemeasurement.Wecanwriteageneralizedcodeandprovideourdifferentqueuesinasimilarway:

privatestaticlongMeasure(IConcurrentQueue<string>queue)

{

varthreads=Enumerable

.Range(0,_writeThreads)

.Select(n=>newThread(()=>

{

for(inti=0;i<_iterations;i++)

{

queue.Enqueue(i.ToString());

Thread.SpinWait(100);

}

}))

.Concat(new[]{newThread(()=>

{

varleft=_iterations*_writeThreads;

while(left>0)

{

stringres;

if(queue.TryDequeue(outres))

left--;

}

})

})

.ToArray();

varsw=Stopwatch.StartNew();

foreach(varthreadinthreads)

thread.Start();

foreach(varthreadinthreads)

thread.Join();

sw.Stop();

if(!queue.IsEmpty)

thrownewApplicationException("Queueisnotempty!");

returnsw.ElapsedMilliseconds;

}

Thelastthingthatweneedisjustruntheprogramandtheresultsaregoingtobelikethis:

privateconstint_iterations=1000000;

privateconstint_writeThreads=8;

publicstaticvoidMain()

{

Console.WriteLine("Queue:{0}ms",Measure(newQueueWrapper<string>()));

Console.WriteLine("LockFreeQueue:{0}ms",Measure(new

LockFreeQueueWrapper<string>()));

www.EBooksWorld.ir

Page 101: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Console.WriteLine("ConcurrentQueue:{0}ms",Measure(new

ConcurrentQueueWrapper<string>()));

}

Theoutputisasfollows:

Queue:3453ms

LockFreeQueue:1868ms

ConcurrentQueue:1162ms

Theseresultsshowthatourlock-freequeuehasanadvantageoverstraightforwardlocking,butthestandardConcurrentQueueperformsbetter.Itusescomplicatedwaysofstoringdata—alinkedlistofarraysegments,whichallowsustoorganizeamoreoptimalprocessofstoringandreadingdata.

www.EBooksWorld.ir

Page 102: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 103: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,wehavelearnedhowwecansynchronizeconcurrentaccesstoshareddatawithoutlocking.Wefoundoutwhatamemorymodelandatomicoperationareandhowthe.NETFrameworkallowsprogrammerstousethemincode.Wehavediscussedthemajorproblemsrelatedtolock-freeprogrammingandmadesurethatatomicityisnecessary,butnotenoughtomaketheconcurrentcodeworkright.Also,wehaveimplementedalock-freestackandqueueandillustratedthelock-freeapproachwithconcreteexamples.

Inthenextchapter,wewillcombineapproachesthatwehavelearnedsofarandseehowwecanstructureaconcurrentprogramtolowertheperformanceoverheadandoptimizeit,dependingonwhatexactlytheprogramdoes.

www.EBooksWorld.ir

Page 104: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 105: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter3.UnderstandingParallelismGranularityOneofthemostessentialtaskswhenwritingparallelcodeistodivideyourprogramintosubsetsthatwillruninparallelandcommunicatebetweeneachother.Sometimesthetasknaturallydividesintoseparatepieces,butusuallyitisuptoyoutochoosewhichpartstomakeparallel.Shouldweuseasmallnumberoflargetasks,manysmalltasks,ormaybelargeandsmalltasksatthesametime?

Theoreticallyspeaking,itdoesnotmatter.Incaseofanidealcomputationaldevice,itwouldhavenooverheadforcreatingaworkerthreadanddistributingworkbetweenanynumbersofthreads.However,onarealCPU,thisperformanceoverheadissignificantanditisveryimportanttotakethisintoaccount.Therightwaytosplityourprogramintoparallelpartsisthekeytowritingeffectiveandfastprograms.Inthischapter,wearegoingtoreviewthisproblemindetail.

www.EBooksWorld.ir

Page 106: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ThenumberofthreadsOneoftheeasiestwaystosplityourprogramintoaparallelexecutingpartisusingthreads.However,whatisathread’scostfortheoperatingsystemandCPU?Whatnumberofthreadsisoptimal?

InWindowsandinthe32-bitmode,themaximumnumberofthreadsinyourprocessisrestrictedbythevirtualaddressspaceavailable,whichistwogigabytes.Athreadstack’ssizeisonemegabyte,sowecanhavemaximum2,048threads.Ina64-bitOSfora32-bitprocess,itshouldbe4,096.Howeverinpractice,theaddressspacewillbefragmentedandoccupiedbysomeotherdata,andthereareotherreasonswhythemaximumnumberofthreadscanbesignificantlydifferent.

Thebestwaytofindoutwhat’sgoingonistowriteacodethatchecksourassumptions.Herewewillprintthecurrentsizeofahandle,givingusawaytodetectwhetherwearein32-bitor64-bitmode.Thenthecodewillstartnewthreadsuntilwegetanyexception,anditwillprintoutthenumberofthreadsthatwewereabletostart:

Console.WriteLine(IntPtr.Size);

varcnt=0;

try

{

for(vari=0;i<int.MaxValue;i++)

{

newThread(()=>Thread.Sleep(Timeout.Infinite)).Start();

cnt++;

}

}

Catch

{

Console.WriteLine(cnt);

}

In32-bitmodeon64-bitWindows,resultscouldbelikethis:

4

1522

Whenweswitchto64-bitmode,wewillgetthefollowing:

8

71926

NotePleasebeawarethatifwerunthisin64-bitmode,theprogramwillexhaustsystemresourcesandmightcausetheOStohang!

In64-bitmode,wehavenotightaddressspacerestrictionsanymore,butthereareotherlimitedresourcessuchasoperatingsystemhandles,kernelmemoryspace,andmore.So,wedonotknowexactlyhowmanythreadsweshouldbeabletorun.However,whyarewegetting1,522threadswhileweexpectedtogetabout4,000whenwecompiledourprogramin32-bitmode?

www.EBooksWorld.ir

Page 107: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Therearetworeasonsbehindthis:

Thefirstreasonisthatwhenweruna32-bitprocesson64-bitWindows,athreadwillhavea64-bitstackaswell,andtheactualstackallocationwillbe1MB+256KBofthe64-bitstack(oreven1MBontheWindowsversionspriortoWindowsVista).Thesecondreasonisthatourprocessislimitedto2GBoftheaddressspace.Ifwewanttousemore,wehavetospecifyaspecialflag,IMAGE_FILE_LARGE_ADDRESS_AWARE,forourprogram,whichissetusingthe/LARGEADDRESSAWARElinkeroption.WecannotsetthisflagdirectlyinVisualStudio,butweareabletouseatoolcalledEditBin.exe,whichisincludedinVisualStudioinstallation.

Tousethistool,justopenVisualStudioDeveloperCommandPromptandrunthefollowingcommand:

editbin/LARGEADDRESSAWAREpath\to\your\program.exe

Toswitchoffthisflag,usethissyntax:

editbin/LARGEADDRESSAWARE:NOpath\to\your\program.exe

Ifyousetthisflagfortheprecedingprogram,youwillseethatweareabletocreateabout3,200threads.Noticethatwecanusetheso-called4-gigabytetuningon32-bitWindows,andusingthisalongwiththeprecedingoption,wecanget3GBofmemoryforour32-bitprocess,whichshouldgiveusabout3,000threads.

However,doweneedtocreatethatmanythreads?Athreadisaquiteexpensiveresource,andifmorethreadsarecreated,morecorrespondingworkhastobeperformedbytheCPU.Besidesthis,moderndesktopCPUssupportonlyafewparallelthreads—from2to12atthemoment.CPUsonservershavemorecoresandcanrunmorethreads,butserver-sideconcurrencyisquitedifferentandwillbereviewedlaterindetailinChapter8,Server-SideAsynchrony.Therefore,creatingmorethreadswillnotmakeaprogrameffective,butinsteaditwillmaketheprogramslower.

Toprovethis,weneedtoexploreamorecomplicatedprogramthanjustusingThread.SpinWaittosimulateCPUload.WewouldliketoseearealcomputationaltaskthatwillinvolveeveryCPU’sblockworkingunderheavyload.Atasklikethiscanbeanimplementationofaraytracingalgorithmtorenderseveralspheres.HereisaquotefromWikipedia:

Incomputergraphics,raytracingisatechniqueforgeneratinganimagebytracingthepathoflightthroughpixelsinanimageplaneandsimulatingtheeffectsofitsencounterswithvirtualobjects.

Itisrelativelyeasytoimplement,anditcanbeeasilyscaledbecausethedifferentpartsofascenehavenoshareddataandcanberenderedindependently.Thefullcodecanbefoundinthecodesamplesofthischapter.TheactualrenderingcodeisplacedinsidetheRenderScenemethod.Itacceptsstartandendcolumnnumbersandanarrayofpixelcolors,whichwillcontaintheresultsofarenderingprocess.

www.EBooksWorld.ir

Page 108: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Inthebeginning,wedefinedthealgorithmparameters.Inthesamplecode,wewillusetheimagedimensionsof1920x1920pixels:

privateconstint_width=1920;

privateconstint_height=1920;

Thismaynotfitintoyourscreen,andtoavoidcomplexity,scrollingwasnotimplementedhere.So,iftheresultantimageistoolarge,youcansimplyloweritssize.However,themeasurementsweretakenfortheinitialimagesize.

Todisplaytherenderingresults,wewillcalltheShowResultmethod.ItcreatesSystem.Drawing.Bitmapwithrenderingresults,createsthePictureBoxcontrolwiththisbitmapdata,andshowsitinWindowsFormsApplication:

privatestaticvoidShowResult(Color[,]data)

{

varbitmap=newBitmap(_width,_height,PixelFormat.Format32bppArgb);

for(vari=0;i<_width;i++)

for(varj=0;j<_height;j++)

bitmap.SetPixel(i,j,data[i,j]);

varpic=newPictureBox{

Image=bitmap,

Dock=DockStyle.Fill

};

varform=newForm{

ClientSize=newSize(_width,_height)

};

form.KeyDown+=(s,a)=>form.Close();

form.Controls.Add(pic);

Application.Run(form);

}

Then,wecanrunthiscodelikethis:

vardata=newColor[_width,_height];

RenderScene(data,0,_width);

ShowResult(data);

Torenderthescene,therearetwoloopsgoingthroughtheXandYcoordinates.Tomaketherenderingprocessparallel,wecanusetheXcoordinatetosplitthecalculationsbetweenworkerthreads,soeachthreadwillrenderitsowncolumnsset.Thenwewillincreasetheworkerthreadsnumberandrepeattheprocesstomeasureperformance:

for(varthreadCnt=1;threadCnt<=32;threadCnt++)

{

varpart=_width/threadCnt;

varthreads=Enumerable.Range(0,threadCnt).Select(

n=>{

varstartCol=n*part;

varendCol=n==threadCnt-1

?_width-(threadCnt-1)*part-1

:(n+1)*part;

returnnewThread(()=>RenderScene(data,startCol,endCol));

www.EBooksWorld.ir

Page 109: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

}).ToArray();

varsw=Stopwatch.StartNew();

foreach(varthreadinthreads)

thread.Start();

foreach(varthreadinthreads)

thread.Join();

sw.Stop();

Console.WriteLine("{0}threads.Rendertime{1}ms",threadCnt,

sw.ElapsedMilliseconds);

}

Thisistherenderingresult:

ThisisthedependencybetweenthenumberofworkerthreadsandtheoverallperformanceonaCorei72600KCPUanda64-bitOS:

www.EBooksWorld.ir

Page 110: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thischartshowsthreemainstages.Thefirststageisasignificantperformanceimprovementwhenweincreasethethreadnumberuptofour.AnIntelCorei72600KCPUhasfourphysicalcores,andloadingallthecoresgivesusalmostlinearscalability.Thenwecanhaveasmootherperformancechangewhilegoingfromfourtoeightthreads.ThisisduetothefactthatthisCPUsupportshyperthreadingtechnology.Thehyper-threadedcoresareimplementedwithasecondsetofhardwareregistersinthesamecore,buttheyusethesamecomputepipeline.Withoutgoingintotoomuchdetail,wecansaythatthisoftencanbeveryefficientandcanperformalmostliketwophysicalCPUcores.Inthisexample,wecanseethatthehyperthreadingtechnologyallowstheprogramtorunfaster.

Thelaststageiswhenweincreasethethreadsnumberfrom8to32.Thelinegoesslightlyupandthismeansthatwedonotgainanyadvantageandonlyloseperformance.TheCPUcannotperformfasterbecausewehavealreadyputthemaximumworkloadonitandcreatingmorethreadsonlyleadstocreatingmoreworkforrunningthethreadsandnotcalculations.

Thus,themosteffectiveoptionisusingasmanythreadsascoresyourCPUhasandasmanylogicalcoresyouroperatingsystemsupports.16threadsisacommonnumberthatwillbeenoughformostofthepresentandnearfuturedesktopCPUs.TheotheroptionistousetheEnvironment.ProcessorCountvariabletoknowduringruntimehowmanycorestheconcreteCPUhas.

NotePleasenoticethatingeneralyoushouldnotusethreadsdirectly.Thereareotherpossibilitiesofrunningtasksinparallel,andyoushouldusethreadsonlywhenyouare

www.EBooksWorld.ir

Page 111: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

100%awareoftheadvantagesanddisadvantagesofotherapproaches.We’llreviewsomeofthemlaterinthisbook.

www.EBooksWorld.ir

Page 112: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 113: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UsingthethreadpoolAsalreadymentioned,creatingathreadisquiteanexpensiveoperation.Inadditiontothis,creatingmoreandmorethreadsisnotefficient.Tomakeasynchronousoperationseasier,inCommonLanguageRuntimethereisathreadpool,whichisrepresentedbytheSystem.Threading.Threadpoolclass.Insteadofcreatingathreadeverytimeweneedone,weaskthethreadpoolforaworkerthread.Ifithasathreadavailable,athreadpoolreturnsittous.Whenitsjobisdone,itgoesbackintothethreadpoolinasuspendedstateuntilitisneededagain.

Therearetwotypesofthreadsinsidethethreadpool:workerthreadsandinput/outputthreads.I/OthreadsareusedforasynchronousI/Oprocessingandwearenotgoingtoreviewthemhere.Let’sconcentrateonworkerthreadsinstead.ThisiswhatMSDNstatesaboutthreadpoolanditslimits:

Thereisonethreadpoolperprocess.

Beginningwiththe.NETFramework4,thedefaultsizeofthethreadpoolforaprocessdependsonseveralfactors,suchasthesizeofthevirtualaddressspace.AprocesscancalltheGetMaxThreadsmethodtodeterminethenumberofthreads.

ThenumberofthreadsinthethreadpoolcanbechangedbyusingtheSetMaxThreadsmethod.

Eachthreadusesthedefaultstacksizeandrunsatthedefaultpriority.

Ifwetrytoacquiremoreworkerthreadsthanthethreadpool’slimit,thesubsequentrequestswillbequeuedandwillwaituntilaworkerthreadbecomesavailable.So,wecannothavemorethreadpoolworkerthreadsthanitslimitatatime.

Inpractice,thethreadpoolimplementationisverycomplicatedandreliesonempiricassumptions.Also,ithasbeenchangedwithnew.NETFrameworkversions,anditispossiblethatitwillbechangedinfuture,soweshouldnotrelyonspecificimplementationdetails.

However,thecommonlogicissimple;thethreadpoolmaintainsasmallnumberofworkerthreadsandcreatesmorethreadswhenneededuntilthelimitisreached.Toseehowthisworks,wecanwriteacodethatwillcreatethreadpoolworkerthreadsandseehowmanythreadsarebeingallocatedatatime:

for(vari=0;i<_threadCount;i++)

ThreadPool.QueueUserWorkItem(

s=>

{

Interlocked.Increment(ref_runCount);

Thread.Sleep(5000);

Interlocked.Decrement(ref_runCount);

});

Thread.Sleep(1000);

while(_runCount>0)

{

Console.WriteLine(_runCount);

www.EBooksWorld.ir

Page 114: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thread.Sleep(100);

}

Weenqueueanumberofworkerthreads,andeachofthemincrementsathreadcounter,thenwaitsfor5seconds,andthendecrementsthecounter,signalingthatitsworkisfinished.Inthemainthread,weprintoutthiscountertoseehowtheworkerthreadsarebeingallocated.

Forthe.NETFramework4.5andaspecifichardware,thiscodeshowsthatatfirstwealmostimmediatelyhavenineworkerthreads,thenthecountergrowsslowlyuntil35-40,andthenitgoesbackto0.Thus,usingthethreadpoolwithalargenumberoftasksallowsustoeffectivelyloadtheCPUandabstractfromtheactualthreadsusagespecifics.

Thereisonemoreworthwhilethingtomentionaboutthethreadpoolthatcanbegoodorbadindifferentscenarios.Thereisonethreadpoolperprocess,andeverylibraryandframeworkthatyouusecanpotentiallyworkwiththethreadpool,sosomeoftheworkerthreadscanbealreadybusywiththird-partycodetasks.So,ifsomelibraryisnotwellwrittenandoccupiesmanyworkerthreadsorblocksthemwithlong-runningoperations,thenyourprogramwillnotbeabletoeffectivelyloadtheCPU.Also,thiscanbecausedbyathird-partycodethatworkswithinput/outputoperationincorrectly,whichleadstoperformancedegradationaswell.

SoifyourprogramusesthethreadpoolforcomputationtasksandtheCPUisnotfullyloaded,itisworthcheckinghowmanyworkerthreadsarethereandwhatexactlytheyaredoing.Specifically,thisisextremelyimportantforserver-sideconcurrency,whereserverframeworksusuallysharethethreadpoolwithyourcode.

www.EBooksWorld.ir

Page 115: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 116: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UnderstandinggranularityWhenthereisonecommoncomputationaltaskinsideyourapplication,itisquiteobvioushowtomakeitruninparallel.ThemosteffectivesolutionwouldbetodividethetasksinpartsandrunthesepartsoneachavailableCPUcore.Sincethenumberofpartswillnotbelarge,therewillnotbeanysignificantperformanceoverhead.Thiswayofdividingyourcodeintoparallelrunningtasksiscalledcoarse-grained:

Thereisoneproblemwiththecoarse-grainedapproach.Thelargetaskscanrunatsignificantlydifferenttimes,andthenatthesetimes,someoftheCPUcoreswillnotbeusedtohelpcomputetheothertasks.OnemorepossibilityisthatthesetaskscanblocktheCPUcoreswhilewaitingforsomesignalsfromotherthreadsorinput/outputoperationtocomplete.Inthiscase,theCPUtimewouldbewasted.

Tobemoreeffective,wewillhavetosplitthesetasksintomoreparts.Ifthenumberofpartswillbeless,thenwewillstillhavetheproblemofsometasksrunningmuchfasterthanothersdoandsomeofCPUcoreswillbeunavailableforfurthercomputations.So,wehavetosplitthetasksintomanysmallpiecesuntilwecansaythatblockingonetaskisnotimportantbecausetheCPUcanswitchtoanothertaskatonce.Thisapproachiscalledfine-grained:

www.EBooksWorld.ir

Page 117: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Howcanweimplementsuchaprogram?Wehavetodivideourcomputationintoverysmalltasksandminimizetheoverheadforeachtasksincetherewillbemanyofthem,andwedonotwanttowasteCPUtimetosupportthesetasks’infrastructureinsteadofdoingcomputation.Thenwehavetofindawaytorunthesetaskseffectively.

Itisverycomplicatedtowriteageneralalgorithmtodividemanydifferentcomputationtasksintoseveralworkerthreads.Fortunately,suchframeworksalreadyexistandoneofthemisincludedinthe.NETFramework.ItiscalledTaskParallelLibrary(TPL).WewilldiscussTPLindetailinChapter4,TaskParallelLibraryinDepth.

Now,wewilluseTPLtowriteafine-grainedparallelprogram.WesimulatethatthetasksaredifferentbyrunningSpinWaitwithdifferentnumberofcycles.Thenwesplitourtasksintodifferently-sizedpiecesandcalculatethenumberofiterationspermillisecondthatwewereabletorun.

Thesamplecodewillbeasfollows:

varrandom=newRandom();

vartaskSizes=

Enumerable

.Range(0,_totalSize)

.Select(n=>random.NextDouble())

.ToArray();

for(varworkSize=256;workSize>0;workSize-=4)

{

www.EBooksWorld.ir

Page 118: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

vartotal=0;

vartasks=newList<Task>();

vari=0;

while(total<_totalSize)

{

varcurrentSize=(int)(taskSizes[i]*workSize)+1;

tasks.Add(

newTask(

()=>

{

Thread.SpinWait(currentSize*_sizeElementaryDelay);

}));

i++;

total+=currentSize;

}

varsw=Stopwatch.StartNew();

foreach(vartaskintasks)

task.Start();

Task.WaitAll(tasks.ToArray());

sw.Stop();

Console.WriteLine(

"Worksize{0},

Taskcount{1},Effectiveness{2:####}works/ms",

workSize,tasks.Count,

((double)total*_sizeElementaryDelay)/sw.ElapsedMilliseconds);

}

}

ThefundamentalentityinTPListheSystem.Threading.Taskclass,whichrepresentsabasictaskthathastoberun.Tocomparetheperformanceoflargetasksversussmalltasks,wewillgothroughthefollowingprocess:

Weprepareanarrayofrandomtasksizestocreateauniquesetoftasksforeachtimeweruntheprogram.Thenwesplitthetotalworkintoasmallnumberoflargetasks,runthemeasurement,andthenrepeatthewholeprocessonceagainbysplittingtheworkintosmallertasksandmakingthetotalnumberoftaskslarger.EachmeasurementinvolvesstartingStopwatch,runningalltasks,waitingforallthetaskstocompletewiththeTask.WaitAllmethod,andthenmeasuringhowmuchtimeittooktocompleteallthetasks.

Hereissamplechartillustratingtheresultsofrunningthiscode:

www.EBooksWorld.ir

Page 119: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thischartshowsthatwhenwereducetasksize,weincreaseperformanceuntilsomepoint.ThenthetasksizebecomessmallenoughtoachievefullCPUworkload.Makingtaskssmallerbecomesineffectiveduetoanoveralltaskoverheadincrease.

Thiswasasynthetictest.Inpractice,everythingwilldependonaprogram’snature.Ifitispossibletovarythetasksizeforyourprogram,andifperformanceiscrucial,youcanrunseveraltestsandfindoutthebestparametersexperimentally.

www.EBooksWorld.ir

Page 120: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 121: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Choosingthecoarse-grainedorfine-grainedapproachFine-grainedparallelismgranularityallowsustorunheterogeneouscomputationaltaskseffectively.Besidesthis,thefine-grainedapproachmakesthesplittingofyourprogramintotaskseasier,especiallyifthesetasksarerelatedtoeachotherand,forexample,lattertasksusesomecomputationresultsofformertasks.However,wewillhavetotradeoffsomeperformance,sincetheCPUhastobeusedtomanageallthesetasksaswell.

Tofindouthowfine-grainedgranularitycanaffectperformanceforarealtask,let’simplementaraytracingalgorithmusingTPLandcompareittotheresultsthatwegotinthebeginningusinganoptimalnumberofthreads.Toimplementthefine-grainedprogramversion,wewilljustcreateataskforeachimagecolumnandstartitimmediately.Theimplementationcodeisasfollows:

vartasks=newList<Task>();

varfineSw=Stopwatch.StartNew();

for(vari=0;i<_width;i++)

{

varcol=i;//Createseparatevariableforclosure

tasks.Add(Task.Factory.StartNew(()=>RenderScene(data,col,col)));

}

Task.WaitAll(tasks.ToArray());

fineSw.Stop();

Console.WriteLine("Finegrained{0}ms",fineSw.ElapsedMilliseconds);

Executingthiscodeincoarse-grainedmodetakesabout150millisecondsonthespecifichardware.Afine-grainedmodetakesabout160milliseconds.Atfirstglance,thedifferenceisinsignificant.However,itisstillnoticeable,evenafterknowingthattheTPLcodeisverywelloptimized.So,ifperformanceisveryimportant,itispossibletotryimplementingparallelismgranularitybyyourself.Howeverbeforethis,youmustbeabsolutelysurethatthebottleneckisgranularityandtheresultsofthetestsconductedapprovethis.

Ifnot,justuseTPLandfine-grainedapproach,whichiseasytocodeandstillprovidesgoodperformance.

www.EBooksWorld.ir

Page 122: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 123: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,wehavereviewedaproblemofparallelcomputationsgranularity.Wehavetrieddifferentwaystosplitourprogramintoconcurrentlyexecutingpiecesandsawtheperformanceimpactineachcase.Also,we’veimplementedarealcomputationtaskofrenderingsphereswitharaytracingalgorithmandlearnedtoparallelizeitwiththreadsandTaskParallelLibrary.

Inthenextchapter,wewillcontinuetolearnTaskParallelLibrary.Weshallreviewthisframeworkindetailandclarifyeveryaspectofusingitincludinghowthetasksarebeingrun,howwecombinetaskstogether,andhowtohandleexceptionsandtimeouts.

www.EBooksWorld.ir

Page 124: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 125: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter4.TaskParallelLibraryinDepthInthepreviouschapter,wehavealreadyusedTPLtosimplifythewritingofsomefine-grainedparallelcode.Thecodelookedquiteclear;however,TPLisaquitecomplicatedframeworkwithahighlevelofabstraction,anditdeservesadetailedreview.

Mostcodesamplesthatwehaveseensofarwerequitesimpleintermsofcomposition.Wetookacomputationalproblem,splititintoseveralindependentparts,andranthesepartsondifferentworkerthreads.Whenallthepartsarecompleted,wegettheirresultsandcombinethemintoafinalcalculationresult.However,mostreal-worldprogramsusuallyhaveacomplexstructure.Weneedtogetinputdata,andthenthereareprogramstagesthatdependoneachother;tocontinuethecalculations,wehavetogetresultsfrompreviousstages.Thesestagescantakedifferentdurationstocompleteandrequiredifferentapproachesforparallelization.

Itispossibletowritethislogicbasedonworkerthreadsandsynchronizationprimitives.However,withmanypartsanddependencies,suchcodewillbecometoolargeandverbose.Tomaketheprogrammingeasier,wecantakeadvantageofdifferentparallelprogrammingmodelimplementationsthatabstractthreadsandsynchronizationmechanicsandoffersomekindofahigher-levelAPIthatismucheasiertouse.ThisistheparallelprogrammingmodeldefinitionfromWikipedia:

Incomputersoftware,aparallelprogrammingmodelisamodelforwritingparallelprogramswhichcanbecompiledandexecuted.Thevalueofaprogrammingmodelcanbejudgedonitsgenerality:howwellarangeofdifferentproblemscanbeexpressedforavarietyofdifferentarchitectures,anditsperformance:howefficientlytheyexecute.Theimplementationofaprogrammingmodelcantakeseveralformssuchaslibrariesinvokedfromtraditionalsequentiallanguages,languageextensions,orcompletenewexecutionmodels.

Onesuchmodelistask-basedparallelism.Itsmainconceptisatask,whichisjustapieceofsynchronouslyexecutingcode.Ifonetaskdependsonanothertask’sresult,wecanprovidesuchinformationtotheframework.Thefinalpartisthetaskscheduler.Itknowsaboutthecurrentenvironmentandcanexecutetasksonanoptimalnumberofthreads,takingintoaccounttheinformationaboutdependenciesbetweenthetasks.Theprogramcodetransformsintodefiningtasksandtheirdependencies,whichismuchcleanerthanrawthreadsorthreadpoolusage.

Letusreconsideracodesamplefromthepreviouschapter:

for(vari=0;i<_width;i++)

{

varcol=i;//Createseparatevariableforclosure

tasks.Add(Task.Factory.StartNew(()=>RenderScene(data,col,col)));

}

Task.WaitAll(tasks.ToArray());

Here,wehaveusedalooptoiteratethroughallthecolumnsofourscene,andthenwesplitcalculationstocreateaseparatetaskforeachcolumn.Tocreatesuchtasks,weusetheSystem.Threading.Taskclass.TheStartNewmethodcreatesanewTaskinstanceand

www.EBooksWorld.ir

Page 126: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

startsthetaskatonce.Whenwehavecompletedcreatingallthetasks,wewillusetheTask.WaitAllstaticmethodtowaituntilallthetaskscompletetheirjobs.

www.EBooksWorld.ir

Page 127: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TaskcompositionLet’sconsiderasituationwhere,beforerunningatask(let’scallthetask,taskB),wewillneedaresultfromthecalculationofaprevioustask,taskA.Suchdependencybetweentasksisusuallycalledfutureorpromise.Thismeansthat,whenweruntaskA,wedonotknowitsresultbeforethecalculationsarecomplete.Sowestate(makeapromise)that,atsomepointinthefuture,wewillruntaskBassoonaswegettheresultfromtaskA.

Whydoweneedtodeclaredependenciesinaspecificway?Wecanalwayscreatedependenttasksasfollows:

vartaskA=newTask<string>(

()=>

{

Console.WriteLine("TaskAstarted");

Thread.Sleep(1000);

Console.WriteLine("TaskAcomplete");

return"A";

});

taskA.Start();

vartaskB=newTask(

()=>

{

Console.WriteLine("TaskBstarted");

Console.WriteLine("TaskAresultis{0}",taskA.Result);

});

taskB.Start();

taskB.Wait();

Theresultisthis:

TaskAstarted

TaskBstarted

TaskAcomplete

TaskAresultisA

First,wecreateanewtaskAinstanceanduseathreadpoolworkerthreadtoexecutethecodeinsidethistask.

NoteBydefault,TaskParallelLibraryuses.NETasthethreadpooltoruntaskcode.However,itispossibletouseotherwaystoruntasks,andthepartofTPLthatisresponsibleforrunningtasksiscalledthetaskscheduler.Wewillreviewtaskschedulerslaterinthischapter.

TheoutputdisplaysTaskAstartedandsimulatessomecalculationsusingtheThread.Sleepmethod.Atthesametime,wewillcreateanewtaskBinstance,whichusesanotherthreadpoolworkerthreadtorun.ItoutputsTaskBstartedtotheconsoleandthenblocksuntiltaskAcompletes.Then,taskAsignalsitscompletionbyprintingTaskAcompleteandreturnsthe“A”stringasaresult.TaskBgetsasignalthattaskAiscompletedandprintstheresultasTaskAresultisA.

So,itseemsthatwehavesuccessfullycreatedtwodependenttasks.Unfortunately,this

www.EBooksWorld.ir

Page 128: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

codewillbequiteineffectiveandhardtomaintain.Imaginethatweneedmoredependencies.Thiscodewillinturncreatemanytasksthatwilluseothertasks’results,andtounderstanddependencies,areaderwillhavetoanalyzeeachtask’scode.Besidesthis,whentaskAruns,taskBblocksthethreadpoolthread.Itmeansthatwehavejustwastedoneworkerthreadthatisdoingnothingandcannotbeusedtoservesomeotherjob.Ifwecreatemanytasks,wewillsoontakeoveralltheworkerthreadsfromathreadpool,andthisisaverybadpracticethatleadstoscalabilityandperformanceproblems.

Nevertheless,itisobviousthatthereisnosenseinrunningtasksAandBinparallel,sinceBneedsAtocomplete.Torunthiscodesynchronously,wecanmergethecodefromAandB,butthiswouldbreakupprogramlogicandleadusbacktothecoarse-grainedapproach.

Anotherwayistoanalyzedependenciesbetweentasksandusethreadpoolworkerthreadsmoreefficiently.Forexample,donotscheduletaskBcodeexecutionuntiltaskAcodefinishesandreturnsitsresult.Allweneedtodoistodeclareadependencybetweentasksexplicitly,soTPLwillknowwhattaskstorunfirstandwhattodelay.ThisisexactlywhattheTask.ContiueWithmethoddoes.Weusethismethodonaninitialtask,andthisreturnsanothertask(usuallycalledacontinuationtask)thatwillbeexecutedaftertheformertaskcompletes:

vartaskA=newTask<string>(

()=>

{

Console.WriteLine("TaskAstarted");

Thread.Sleep(1000);

Console.WriteLine("TaskAcomplete");

return"A";

});

taskA

.ContinueWith(

task=>

{

Console.WriteLine("TaskBstarted");

Console.WriteLine("TaskAresultis{0}",task.Result);

});

taskA.Start();

taskA.Wait();

WecreatedataskAinstancesimilartothepreviousexample.However,insteadofcreatinganewtask,weusedtheContinueWithmethodonthetaskAinstancethatallowsustoprovideacodethatwillberunwhentaskAcompletes.WehaveaccesstothetaskAinstanceviathetaskparameterofthelambdaexpression.NowtheTPLtaskschedulerwillplaceacontinuationtaskcodeonathreadpoolonlyaftertaskArunstocompletion.

Theresultofthiscodewillbeasfollows:

TaskAstarted

TaskAcomplete

TaskBstarted

TaskAresultisA

Noticethattheorderofmessagesisdifferentthanthepreviousresult.NowtaskBis

www.EBooksWorld.ir

Page 129: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

startedaftertaskAcompletes.

Thiscanbeadisadvantageifthelattertaskperformssomeworkthatcanberuninparallel.Inthiscase,runningtaskBafterAwillbeinefficient,sinceitisactuallyasynchronouscodeexecution.However,TPLisabouttaskcompositionanditsimplymeansthatwecansplittaskBintotwotasks;onewillruninparallelwithtaskAandtheotherwillbeplacedinacontinuationtask:

vartaskA=newTask<string>(

()=>

{

Console.WriteLine("TaskAstarted");

Thread.Sleep(1000);

Console.WriteLine("TaskAcomplete");

return"A";

});

taskA.Start();

vartaskB1=newTask(()=>Console.WriteLine("TaskB1started"));

taskB1.Start();

taskA.ContinueWith(tsk=>Console.WriteLine("TaskAresultis{0}",

tsk.Result));

taskA.Wait();

Ifwerunthiscode,theresultswillshowthattaskAandB1runinparallel;B1canevenberunbeforeA,sinceitdoesnotreallymatterintermsofprogramlogicinwhatorderindependenttasksarescheduledtorun.

Therearemorecomplicatedwaysofcomposingtasks.Forexample,whenataskneedsresultsfrommultipletasks,theContinueWithmethodallowsustofollowonlyonetask,andweneedtaskB2togettheresultsfromAandB1:

However,thereisaTaskFactoryclassthatcanbeaccessedthroughtheTask.Factorystaticproperty.Itcontainsmanyusefulthingstocreateandscheduletasks,butwhatweneednowisitsContinueWhenAllmethod.

Theimplementationofthemultipledependencyschemaisasfollows:

vartaskA=newTask<string>(

www.EBooksWorld.ir

Page 130: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

()=>

{

Console.WriteLine("TaskAstarted");

Thread.Sleep(1000);

Console.WriteLine("TaskAcomplete");

return"A";

});

taskA.Start();

vartaskB1=newTask<string>(

()=>

{

Console.WriteLine("TaskB1started");

Thread.Sleep(500);

Console.WriteLine("TaskB1complete");

return"B";

});

taskB1.Start();

Task

.Factory

.ContinueWhenAll(

new[]{taskA,taskB1},

tasks=>Console.WriteLine("TaskAresultis{0},TaskBresultis

{1}",tasks[0].Result,tasks[1].Result));

taskA.Wait();

TheContinueWhenAllmethodacceptsanarrayoftasksasitsfirstparameterandalambdaexpressionasthesecond.Thelambdaexpressiontasksparameteristhetasksarraythatwehavejustprovided.Insteadofusingthis,itispossibletocreateaclosureandaccessthetaskB1andtaskAvariablesinthelambdabody,butthiswouldcreateunnecessarydependenciesinthecode,whichisgenerallyabadpractice.

NoteThisisoftenreferredtoascodecoupling.Whenthecodehasmanydependencies,itiscalledhighcoupling;inthiscase,thecodeishardtomaintain,sinceanychangecanaffecttheotherparts.Lowcouplingmeansthatthispartofthecodedoesnotdependonotherparts,soitcanbechangedandmaintainedeasilywithoutbreakingtheothercode,andotherchangeswillmostlikelynotbreakthispartoftheprogram.

Theresultswillbeasfollows:

TaskAstarted

TaskB1started

TaskB1complete

TaskAcomplete

TaskAresultisA,TaskBresultisB

ThisshowsthattasksAandB1runindependentlyinparallel,andthefinalcodegetstheresultsfromboththetasks.Wesuccessfullydescribeddependenciesinadeclarativeway,andtheTPLinfrastructureensuredthecorrectnessoftheexecutionorderandprogramlogic.

www.EBooksWorld.ir

Page 131: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ItisworthmentioninganotherTaskFactoryclassmethod,theContinueWhenAnymethod,whichisquitesimilartoContinueWhenAll.Itcreatesataskthatstartswhenanyoftheprovidedtasksinthearraycomplete.Thisisusefulforhavingseveralalternativewaystoachievetheresult,andweusetheonethatcompletesfasterthantheothers.

www.EBooksWorld.ir

Page 132: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 133: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TaskshierarchyWementionedbeforethatthetaskschedulerneedsexplicitlydefineddependenciesbetweentaskstorunthemeffectivelyandinthecorrectorder.However,besidesthis,thereisawaytoachieveimplicitdependencydefinition;whenwecreateonetaskinsideanother,aspecialparent-childdependencyiscreatedforthesetasks.Bydefault,thisdoesnotaffecthowthesetaskswillbeexecuted,butthereisawaytomakethisdependencyreallyimportant.

WecancreateataskwiththeTaskFactory.CreateNewmethodbyprovidingaspecialTaskCreationOptions.AttachedToParentparameter.Thischangestheusualtaskbehavior,andtheimportantdifferencesareasfollows:

Theparenttaskwillnotcompleteuntileverychildtaskcompletes.Ifthecasechildtaskscauseanyexceptions,theywillbetranslatedtotheparenttask.Theparenttaskstatusdependsonitschildtasks.Ifanychildtaskfails,theparenttaskwillhavetheTaskStatus.Faultedstatusaswell.

Toillustratethis,wecancomparethebehaviorofthedefaulttaskandthetaskattachedtotheparent.Here,wewillcreateachildtaskwithoutspecifyingthetaskcreationoptions:

Task

.Factory

.StartNew(

()=>

{

Console.WriteLine("Parentstarted");

Task

.Factory

.StartNew(

()=>

{

Console.WriteLine("Childstarted");

Thread.Sleep(100);

Console.WriteLine("Childcomplete");

});

})

.Wait();

Console.WriteLine("Parentcomplete");

Asaresultwegetthefollowing:

Parentstarted

Childstarted

Parentcomplete

Itisimportantthattheparenttaskhascompletedbeforethechildtask,andsincewewaitedonlyfortheparenttask,themainthreadexitedandthechildtaskdidnotcompleteatall.

NowweaddtheAttachedToParentoptioninthesamecode,changingonlythechildtaskasfollows:

Task

www.EBooksWorld.ir

Page 134: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

.Factory

.StartNew(

()=>

{

Console.WriteLine("Childstarted");

Thread.Sleep(100);

Console.WriteLine("Childcomplete");

},

TaskCreationOptions.AttachedToParent);

Runthisagaintogetthefollowing:

Parentstarted

Childstarted

Childcomplete

Parentcomplete

Here,wecanseethattheparenttaskwaitsuntilthechildtaskfinishesandonlythenchangesitsstatustoTaskStatus.RanToCompletion.

www.EBooksWorld.ir

Page 135: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 136: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

AwaitingtaskcompletionTherearedifferentwaystowaituntiltheTPLtaskcompletes.Inthepreviouscode,weusedtheTask.Waitmethod.Thismethodblocksthecurrentthreaduntilthistaskcompletes.Ifthetaskgivesaresult,thesameeffectcanbeachievedwhentheTask.Resultinstancepropertyisqueried.Thisisabasicwaytocoordinatetasksintheprogram.

Whenweneededtowaitformultipletasks,weusedtheTask.WaitAllstaticmethod.Ifwekeepasidetheoptimizationandexceptionhandlingcode,thismethodwillbeimplementedusingthefollowinglogic:

varwaitedOnTaskList=newList<Task>(tasks.Length);

for(inti=tasks.Length-1;i>=0;i--)

{

Tasktask=tasks[i];

if(!taskIsCompleted)

waitedOnTaskList.Add(task);

}

if(waitedOnTaskList!=null)

{

WaitHandle[]waitHandles=newWaitHandle[waitedOnTaskList.Count];

for(vari=0;i<waitHandles.Length;i++)

waitHandles[i]=waitedOnTaskList[i].CompletedEvent.WaitHandle;

WaitAll(waitHandles);

}

WehavedefinedalistoftasksthatarenotcompletedyetandthenattachedanarrayofhandlestotheOS-specificobjectsthatcanbeusedtowaitforeachtasktobecompleted.Thenwewaitontheseobjectsuntilallunderlyingtasksarecompleted.

Asinthepreviouscase,alongwithWaitAll,theTasktypedefinestheWaitAnystaticmethod.Itwaitsuntilanytaskinthearrayiscompleted.Itcanbeusedtotracktheprogressoftaskcompletionortochoosethefastestwaytogetresultsfromtheseveralalternatives.

www.EBooksWorld.ir

Page 137: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 138: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TaskcancellationAtaskrepresentsacommonasynchronousoperation.Thismeansthatwedon’tknowwhenitcompletes.Sometimes,itisclearthatwedonotneedthistaskanymore.Forexample,iftheoperationtakestoolongtocomplete,ortheuserclicksontheCancelbutton.Inthiscase,weneedtostopthetask.

Oneofthelower-levelwaystostopathreadisbycallingitsAbortmethod.Beforegoingon,Iwouldliketoemphasizetheimportanceofnotusingthis.

NoteNevereveruseThread.Abort!

Thread.AbortraisesaveryspecialexceptioncalledThreadAbortExceptiononathreadthatisbeingaborted.Thisexceptioncanhappenatmoreorlessanypointinyourprogramandcannotbestoppedbytheusualexceptionhandling.Wecanwriteacodewithcatchblockandthecodeinsidethisblockwillwork,butassoonasthecatchblockends,thesameexceptionwillberaisedagain.But—surprise—ifwecalltheThread.CurrentThread.ResetAbortmethodinsidethecatchblock,thethreadabortrequestwillbecanceled.ThismeansthatcallingThread.Abortdoesnotguaranteethatthethreadwillbeactuallyaborted.

Anotheraspectofusingthismethodisthatitaffectsonlythemanagedcode.Ifyourthreadiswaitingforunmanagedcodetocomplete,whichisalmosteveryI/Ooperation,thethreadwillnotbeaborteduntilthisoperationends.Iftheoperationnevercompletes,thecodewillneverreturnandyourprogramwillhang.

Also,duetothe.NETCLRconstructingtypealgorithmspecifics,thisexceptioncanbreakyourprogram.Ifthereisanexceptioninsideastaticconstructorofsometype,thisexceptiongetscached,andallfurtherattemptstousethistypewillleadtothrowingthisexception.SoifwecallThread.Abortandraisethisexceptionwhilethetargetthreadwasexecutinganystaticconstructor,wewillgetThreadAbortExceptionwhenanythreadtriestoaccessthetypethatfailedtobecreatedonthepreviousthread.

Ifthisisnotenough,thereisonemoreillustrationofhowevilthisexceptionis.Imaginethecodethatisusuallywrittenforworkingwithfiles:

using(FileStreamfs=File.Open(fileName,...))

{

...dostuffwithdatafile…

}

Theprecedingcodeistheshorterversionofthefollowingcode:

FileStreamfs=File.Open(fileName,...);

try

{

...dostuffwithdatafile…

}

finally

{

IDisposabledisposable=fs;

www.EBooksWorld.ir

Page 139: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

disposable.Dispose();

}

SinceThreadAbortExceptioncanemergeatanypoint,itcanhappeninsidethefinallyblock.Ifithappensthere,thecodeinthisblockwillnotruntocompletionandthefilewillremainopened.Inthiscase,theFileStreamclassimplementsadisposablepatternandislikelytobeclosedwhileitsfinalizermethodiscalledwhengarbagecollectionoccurs.However,itisclearthatleavingthefileopenforanundefinedtimeisnotagoodthingandothercodeisnotalwayscorrectlywritten.

Therefore,Thread.Abortmustbeavoidedinallcircumstances.Insteadofusingthis,weshouldwritethecodewhilebeingawareofthecancellationpossibility.Itisimportantthatthiscancellationmustnotdependonanyconcretewaysofrunningtheoperationitself,sinceTPLabstractsawaytaskexecutionmechanics,allowingustowritecustomtaskschedulersandusethemwithstandardTPLcode.

Fortunately,the.NETFrameworkcontainsaCancellationAPI,andthisiswhatweshouldusetoimplementcancellationinourcode.TPLusesthisAPIaswell,whichmakesiteasiertowritecancellationcodeforTPL-basedprograms.

TheCancellationAPIisbasedontwomaintypes—theSystem.Threading.CancellationTokenstructureandtheSystem.Threading.CancellationTokenSourceclass.Thecancellationtokencontainsmethodsandpropertiesthatwecanusetohandlethecancellationrequest,andthecancellationtokensourceallowsustocreatecancellationtokensandinitiatecancellationrequests.

Atypicalsituationiswhenwehavetwopartsofacode.Thefirstpartisthecodethatcreates,combines,andrunstasks.ThiscodecaninteractwiththeprogramUIandhandlessituationswhenweneedtocancelsomeoftherunningtasks.Usually,thispartisresponsibleforcreatingCancellationTokenSource,constructingcancellationtokeninstances,andprovidingthemtoeachtaskthatcanbecancelled.Then,whenthecancellationprocessisbeinginitiated,wecalltheCancelorCancelAftermethodsoneachcancellationtokenneeded.

Thesecondpartofcodelivesinsidetasksandusescancellationtokeninstancestogetcancellationsignals.Thereareseveralcommonapproachestoimplementingthecancellationitself.Theyarecoveredinthefollowingsections.

www.EBooksWorld.ir

Page 140: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CheckingaflagIfthecodeinsideataskisquiteeasy,forexample,itisaloopwithshortiterations,thentheeasiestwaytostoptheoperationistochecksomeflagvariableinsidethisloopandexitiftheflagisset.

Thefirstpartofthecodecreatesatask,providesitwithacancellationtoken,andtheninitiatesacancellationprocess.Finally,wemeasurethetimeofthetaskcancellationprocessasfollows:

privatestaticvoidRunTest(Action<CancellationToken>action,stringname)

{

varcancelSource=newCancellationTokenSource();

varcancelToken=cancelSource.Token;

vartask=Task

.Factory

.StartNew(()=>action(cancelToken),cancelToken);

//Waitforstartingtask

while(task.Status!=TaskStatus.Running){}

varsw=Stopwatch.StartNew();

cancelSource.Cancel();

while(!task.IsCompleted){}

sw.Stop();

Console.WriteLine("{0}taskcancelledin{1}ms",name,

sw.ElapsedMilliseconds);

}

Noticethatweareprovidingacancellationtokennotonlytoourtask,butalsototheStartNewmethodaswell.ThereasonforthisisthatTPLisawareofcancellationtokensaswellandcancancelthetaskevenifithasnotstartedyetandourcodeisnotabletohandlecancellation.

Also,weusealoopinsteadofcallingtheWaitmethod.TheWaitmethodhasanoverloadacceptingthecancellationtokeninstance.IfwecalltheCancelmethodfromthetoken,theWaitmethodwillreturntheexecutionatonce.Thisisabuilt-incancellationmechanisminTPL,butweneedcustomcancellationnow,soweemulatewaitingwithtaskstatuscheckinginsidetheloop.First,wewaituntilthetaskactuallystarts,andthenweinitiatecancellationandwaituntilthetaskcompletes.

Finally,westopthetimerandprintouttheresults.

Thecodeforthetaskrunsaninfiniteloop,waits,andcheckswhetheracancellationisrequested:

RunTest(tok=>

{

while(true)

{

Thread.Sleep(100);

if(tok.IsCancellationRequested)

break;

}

www.EBooksWorld.ir

Page 141: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

},"CheckFlag");

Theresultcanbelikethis:

CheckFlagtaskgotcancelledin103ms

Thismeansthatthecancellationhappenedinthefirstloopiterationassoonasthetaskcodecheckedtheflag.

www.EBooksWorld.ir

Page 142: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ThrowinganexceptionIfthecodeinsidethetaskiscomplicated,itisdifficulttochecktheflagineverypartofthecode.Therecanbemanyloopsinsidemanymethods,andifwegetresultsfromamethodthatcanbecancelled,weneedtoprovideadditionalinformationtodistinguishwhetherthismethodwascancelledorsuccessfullyrantocompletion.Inthiscase,itismucheasiertouseanothercancellationtechnique—throwingaspecialcancellationexception.

IfweusetheCancellationToken.ThrowIfCancellationRequestedmethodonourtoken,thenitwillthrowOperationCanceledExceptionwhencancellationisrequested.Thisexceptionwillstopcodeexecutioninsidethetask,bubbleuptotheTPLinfrastructurethatwillhandleit,andsettaskstatustoTaskState.Canceled.

Insteadofcheckingtheflag,weinstructthetokentoraiseOperationCanceledExceptionwhenreceivingacancellationrequest:

RunTest(tok=>

{

while(true)

{

Thread.Sleep(100);

tok.ThrowIfCancellationRequested();

}

},"ThrowException");

Thechangesareminimal,andtheresultshouldbethesame:

ThrowExceptiontaskgotcancelledin109ms

AssoonaswegettotheThrowIfCancellationRequestedmethod,thecalloperationgetscancelledwithanexception.

www.EBooksWorld.ir

Page 143: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UsingOSwaitobjectswithWaitHandleThenextoptionisusefulwhenthecodeinsideataskiswaitingonanOSsynchronizationprimitiveforasignificanttime.Here,wecanuseCancellationToken.WaitHandletoincludeinthewaitingprocessandreactimmediatelywhencancellationisrequested.

Thisisusuallycombinedwithoneofthepreviouslydescribedtechniques—wejuststopwaitingandproceedwiththecancellation.

Thisishowitlooks:

RunTest(tok=>

{

varevt=newManualResetEvent(false);

while(true)

{

WaitHandle.WaitAny(new[]{evt,tok.WaitHandle},100);

tok.ThrowIfCancellationRequested();

}

},"WaitHandle");

Inthisexample,wehavecreatedaManualResetEventinstancetowaitonitinsteadofusingThread.Sleep.However,wehaveusedWaitHandle.WaitAnytoincludethecancellationtokeninthewaitingprocess.So,herewewaitfortheeventortokentobesignaledusingthe100mstimeoutvalueandthencontinuerunningtheloop.

Nowtheresultshouldbedifferentasfollows:

WaitHandletaskgotcancelledin0ms

Sinceweareabletoproceedwiththecancellationassoonasthetokengetssignaled,ithappensalmostimmediately.

www.EBooksWorld.ir

Page 144: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CancellationusingcallbacksItisgoodwhenyoucontrolallthecode,anditispossibletochangeeverypieceofthecodetoimplementcancellationproperly.However,themostcommonsituationiswhenyouusesomeexternalcodeinsideyourtaskandyoudonotcontrolthiscode.Imagineifthisisconnectedviaaslownetworktosomeserverandthisfetchesdata.YoupresstheCancelbutton,buttheoperationwillnotcompleteuntilitfinishestheI/Ooperation.Thisisnotaverygooduserexperienceandcanbeakeyreasonfortheusertochooseadifferentsoftware.

Ofcourse,wecanwritesimilarcodefromscratch.However,usuallywedonotneedto,sincealmosteverythird-partycodesuchasthisprovidessomething,suchastheCloseorDisposemethods,allowingustointerruptcommunicationandreleaseallocatedresources.Theproblemisthatthesemethodscanbeverydifferentineverythird-partyframework.

Fortunately,thecancellationAPIprovidesuswithapossibilitytoregisteranycancellationcodeasacallbackandrunthiscallbackassoonasacancellationisrequested.Toillustratethisapproach,wecanwriteaclient/serverapplicationandimplementacallbackcancellation.

Theserverpartisrelativelysimple.Wejustneedtoallowinboundconnectionandsimulateaslowresponse:

constintport=8083;

newThread(()=>

{

varlistener=newTcpListener(IPAddress.Any,port);

listener.Start();

while(true)

using(varclient=listener.AcceptTcpClient())

using(varstream=client.GetStream())

using(varwriter=newStreamWriter(stream))

{

Thread.Sleep(100);

writer.WriteLine("OK");

}

}){IsBackground=true}

.Start();

Thisserverwilllistenforincomingconnectionsonport8083;whentheconnectionisestablished,itwaitsfor100msandrespondswithanOKstring.

Insideourtask,wearegoingtoconnecttothisserverviatheTcpClientclassandthencanceltheconnectionassoonaspossible:

RunTest(tok=>

{

while(true)

{

using(varclient=newTcpClient())

{

client.Connect("localhost",port);

using(varstream=client.GetStream())

using(varreader=newStreamReader(stream))

www.EBooksWorld.ir

Page 145: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Console.WriteLine(reader.ReadLine());

}

tok.ThrowIfCancellationRequested();

}

},"Callback");

Thissampleprintsthefollowingresult:

OK

Callbacktaskgotcancelledin109ms

Thiscodeconnectstotheserverandwaitsfortheservertorespond;onlyaftergettingtheresponsedoweproceedwiththecancellation.

Accordingtothedocumentation,theTcpClientclassincludestheClosemethod.ThismethodinterruptsworkandclosestheTCPconnectionifithasbeenalreadyopened.Allweneedtodoistocallthismethodwhenacancellationisrequested:

RunTest(tok=>

{

while(true)

{

using(varclient=newTcpClient())

using(tok.Register(client.Close))

{

client.Connect("localhost",port);

using(varstream=client.GetStream())

using(varreader=newStreamReader(stream))

Console.WriteLine(reader.ReadLine());

}

tok.ThrowIfCancellationRequested();

}

},"Callback");

Thedifferenceisjustaddingasinglelineofcode.WecalltheCancellationToken.RegistermethodthatacceptsthecallbackthatwillbecalledinthecaseofcancellationandreturnstheCancellationTokenRegistrationstructure.ItimplementsIDisposableandcallingtheDisposemethodonitwillderegisterthecallback,soitwillnotbecalledifthecancellationhappensafterwards.

Sointhesamplecode,wewouldliketorunclient.Closewhenthecancellationhappensbutonlyinsidetheinnerusingblock.Ifthecancellationhappenssomewhereelse,wedonotneedtorunthiscallback.Asaresult,wewillgetsomethinglikethis:

Callbacktaskgotcancelledin3ms

Nowitisclearthatwedonotwaitfortheservertorespondandcanceltheoperationalmostimmediately.WemanagedtomaketheusershappywithoutrewritingTcpClientfromscratchwiththehelpofthecancellationAPI.

www.EBooksWorld.ir

Page 146: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 147: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Latencyandthecoarse-grainedapproachwithTPLRawperformance,orthenumberofcalculationspersecondthatourprogramisabletoperform,isnotalwaysamostimportantgoaltoachieve.Sometimesitisevenmoreimportanttostayresponsiveandinteractwiththeuserasfastaspossible.Unfortunately,itisnoteasytoachieveboththeseadvantagesatthesametime;therearesituationswhenweneedtochooseourprimarygoal.

Tosimulatesuchasituation,let’screateacombinationofcoarse-grainedcomputationaltasksthattakesalongtimetocompleteandrunsinthebackground,andanumberofshort-livedtasksrepresentinguserinteraction.Wewouldliketheseshorttaskstorunasfastaspossiblewithlowlatency.Nowwewriteacodetotesthowtheselong-runningtaskscanaffectlatency:

for(varlongThreadCount=0;longThreadCount<24;longThreadCount++)

{

//Createcoarsegrainedtasks

varlongThreads=newList<Task>();

for(vari=0;i<longThreadCount;i++)

longThreads.Add(

Task.Factory.StartNew(

()=>Thread.Sleep(1000)));

//Measurelatency

varsw=Stopwatch.StartNew();

for(vari=0;i<_measureCount;i++)

Task

.Factory

.StartNew(()=>Thread.SpinWait(100))

.Wait();

sw.Stop();

Console.WriteLine("Longrunningthreads{0}.Averagelatency{1:0.###}

ms",longThreadCount,(double)sw.ElapsedMilliseconds/_measureCount);

Task.WaitAll(longThreads.ToArray());

}

Wehavecreatedupto24longrunningthreadsinsidetheloop,andineachiteration,wemeasuredupanaveragelatencyofrunningashorttask.Finally,wewaitforalltaskstocompleteandprintoutresults.Thisishowtheresultdatalooksonachart:

www.EBooksWorld.ir

Page 148: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Wecanseethatwehaveaverylowlatencyuntileightlongrunningtasks,andthenitdramaticallyincreasesupto4-5times.Thereasonis,asusual,complex,butthemainreasonisthattheCPUinthiscasesupportsuptoeightsimultaneouslyrunningthreads.Whilelong-runningtasksoccupiedfewerthreadsthanthislimit,theremainingthreadscanbeusedtoexecuteshort-livedtasks.Assoonastherearenofreethreadsremaining,shorttaskshavetocompeteforthreadpoolworkerthreadsandshareCPUtimewiththelong-runningtasks,andthustheshorttasksbecomemuchslower.

Tomakeshorttasksfasteragain,wecanisolatelongtasksfromthethreadpoolthatrunstheshorttasks.Iftheshorttaskshavepriorityingettingresources,thentheywillrunfaster,andthelong-runningtaskswillrunabitslower,buttheshort-tasklatencywillbemuchbetter.

TPLhasanoptiontospecifythatataskislong-runningandshouldbetreatedinaspecialway:

Task.Factory.StartNew(

()=>Thread.Sleep(1000),

TaskCreationOptions.LongRunning)

In.NET4.5,thedefaulttaskschedulerrunssuchtasksonseparatethreadsthatarenotthreadpoolthreads.ThisiswhatthereferenceimplementationoftheThreadPoolTaskSchedulermethodofQueueTasklookslike:

protectedinternaloverridevoidQueueTask(Tasktask)

{

if((task.Options&TaskCreationOptions.LongRunning)!=

TaskCreationOptions.None)

{

www.EBooksWorld.ir

Page 149: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

newThread(s_longRunningThreadWork){IsBackground=true

}.Start(task);

}

else

{

boolforceGlobal=(task.Options&TaskCreationOptions.PreferFairness)

!=TaskCreationOptions.None;

ThreadPool.UnsafeQueueCustomWorkItem(task,forceGlobal);

}

}

However,ingeneral,wedonotknowhowsuchtaskswillbetreated,andthewayofrunningsuchtasksistotallyuptothecurrenttaskschedulerimplementation.

Addingnewresultstothechartgivesusthis:

Itseemsthatwesuccessfullyresolvedlatencyissue.Ofcourse,thelong-runningtaskswillbeslightlyslower,butthisiswhatwewantedtoachieve.

www.EBooksWorld.ir

Page 150: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 151: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ExceptionhandlingAnotherimportantaspectofTPLisworkingwithexceptions.Justasthenormalcodethatwewritecangenerateanexception,socanthecodeinsideaTPLtask.Sinceeverytaskhasitsownstack,wecannotworkwithexceptionsintheusualway.TPLhasseveraloptionsthatallowustoworkwithexceptionsinaparallelprogram.

Theeasiestoptionistocheckthetaskstatus.Ifanexceptionhasbeenraisedinsidethetask,itwillhavetheStatuspropertysettoTaskStatus.Faulted.TheexceptionwillbeavailablethroughtheTask.Exceptionproperty:

vartask=Task.Factory.StartNew(()=>

{

thrownewApplicationException("Testexception");

});

while(!task.IsCompleted){}

Console.WriteLine("Status={0}",task.Status);

Console.WriteLine(task.Exception);

Thiscodeprintsthefollowing:

Status=Faulted

System.AggregateException:Oneormoreerrorsoccurred.--->

System.ApplicationException:Testexception

...

TheoriginalexceptionthathasbeenthrowninthecodebecamewrappedinanAggregateExceptioninstance.Thereasonisthattherecanbemanyexceptionsfromchildtasksthatruninparallel.Intheaggregateexceptioninstance,thereistheInnerExceptionspropertythatwillcontainallthewrappedexceptions.

Towaitforthetaskcompletion,wehaveusedaloopinsteadoftheTask.Waitmethod.Whenataskcompleteswithanexception,thismethodwillrethrowtheexceptiononthethreadthathascalledWait.Ifwereplacethewhileloopwiththetask.Wait()methodcallandrunthecodeagain,wewillseeanunhandledexception:

UnhandledException:System.AggregateException:Oneormoreerrors

occurred.--->System.ApplicationException:Testexception

...

ThesamebehaviorwillhappenwhenweusetheTask.ResultpropertyortheTask.WaitAll/WaitAnystaticmethods.

Whenreviewingparent-childrelationsbetweentasks,wehavestatedthat,ifwecreateachildtaskwithTaskCreationOptions.AttachedToParent,thenitsexceptionswillautomaticallybepropagatedtotheparenttask.Tochecktheexceptionbehavior,wecanquicklycreatetwonestedtasksandthrowanexceptionfromthechildtask:

Task.Factory.StartNew(()=>

{

Task.Factory.StartNew(()=>

www.EBooksWorld.ir

Page 152: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

thrownewApplicationException("Testexception");

},TaskCreationOptions.AttachedToParent);

})

.Wait();

Thiswillprintthefollowing:

UnhandledException:System.AggregateException:Oneormoreerrors

occurred.--->System.AggregateException:Oneormoreerrorsoccurred.--->

System.ApplicationException:Testexception

...

Asweexpected,theparenttaskcompletedwiththeexceptionthatbubbledfromitschildtask.However,nowwehaveanaggregateexceptionthatcontainsanotheraggregateexception,whichinturncontainstheinitialexceptionfromthechildtask.Theexceptionhierarchyrepeatsthetaskrelationship,whichisnotalwaysagoodthing.

Wemayputthepreviouscodeinatryblockandwriteacatchblocktoprinttheinnerexceptionsasfollows:

catch(AggregateExceptionae)

{

foreach(Exceptioneinae.InnerExceptions)

{

Console.WriteLine("{0}:{1}",e.GetType(),e.Message);

}

}

Theresultsoftheprecedingcodecanbesurprising:

System.AggregateException:Oneormoreerrorsoccurred.

Sinceitisahierarchy,weneedtocheckinnerexceptionsinsideeachaggregateexceptionthatweget.Sincetheaggregateexceptionisonlyacontainerforarealexception,weactuallyneedtocollectonlytheotherexceptions.Fortunately,thereisawaytoflattentheexceptionhierarchyintoasimplecollectionofinitialexceptions.Tocheckthis,let’screateacomplextaskstructureandseewhatisinsidethetop-levelexception:

vart=Task.Factory.StartNew(()=>

{

Task.Factory.StartNew(

()=>

{

Task.Factory.StartNew(

()=>

{

thrownewApplicationException("Andweneedtogodeeper");

},TaskCreationOptions.AttachedToParent);

thrownewApplicationException("Testexception");

},TaskCreationOptions.AttachedToParent);

Task.Factory.StartNew(()=>

{

thrownewApplicationException("Testsiblingexception");

www.EBooksWorld.ir

Page 153: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

},

TaskCreationOptions.AttachedToParent);

});

try

{

t.Wait();

}

catch(AggregateExceptionae)

{

foreach(Exceptioneinae.Flatten().InnerExceptions)

{

Console.WriteLine("{0}:{1}",e.GetType(),e.Message);

}

}

Asaresult,wewillgetalistofalltheinitialexceptions:

System.ApplicationException:Testsiblingexception

System.ApplicationException:Testexception

System.ApplicationException:Andweneedtogodeeper

Oneofthecancellationoptionsthatwehavereviewedsofarwasthrowingaspecialkindofexception,OperationCanceledException.TPLtreatsthisexceptioninaspecialway.ThetaskstatuswillbeTaskStatus.CanceledinsteadofFaulted,andtheExceptionpropertywillbeempty:

varcancelSource=newCancellationTokenSource();

vartoken=cancelSource.Token;

vartask=

Task

.Factory

.StartNew(

()=>

{

while(true)

token.ThrowIfCancellationRequested();

},

token);

while(task.Status!=TaskStatus.Running){}

cancelSource.Cancel();

while(!task.IsCompleted){}

Console.WriteLine("Status={0},IsCanceled={1}",task.Status,

task.IsCanceled);

Console.WriteLine(task.Exception);

Theresultshowsthatacancellationexceptioninthiscaseisbeingtreateddifferently:

Status=Canceled,IsCanceled=True

NotePleasenoticethatifwedonotpassatokeninstanceasthelastparameteroftheStartNewmethod,thecancellationexceptionwillbetreatedlikearegularexception.

www.EBooksWorld.ir

Page 154: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 155: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UsingtheParallelclassTPLprovidesareachAPItocomposeaparallelprogram.However,itisquiteverbose,andifwewriteasimplecode,thereareeasierwaytoparallelizeit.Forcommontaskssuchasrunningsomecodeinparallelandparallelizingtheforandforeachloops,thereisaParallelclassthatprovidesasimpleandeasytouseAPI.

www.EBooksWorld.ir

Page 156: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Parallel.InvokeThismethodexecutesactionsinparalleliftheCPUhasmultiplecoresandsupportsmultiplethreads.IftheCPUhasonlyonecore,actionswillbeexecutedsynchronously.Thismethodblocksthecallingthreaduntilalltheactionsarecompleted:

Parallel.Invoke(

()=>Console.WriteLine("Action1"),

()=>

{

Thread.SpinWait(10000);

Console.WriteLine("Action2");

},

()=>Console.WriteLine("Action3"));

Console.WriteLine("End");

Afterrunningtheprecedinglinesofcode,wegetthefollowingoutput:

Action1

Action3

Action2

End

WecanprovidetheParallelOptionsclassinstancetothismethodtoconfigureadditionaloptionssuchaslimitingtheparallelismdegree,specifyingacancellationtoken,andusingaspecificimplementationofthetaskschedulertoruntasksonit.

Thestraightforwardimplementationofthismethodwillbeasfollows:

vartasks=newList<Task>();

foreach(varactioninactions)

{

tasks.Add(Task.Factory.StartNew(action));

}

Task.WaitAll(tasks.ToArray());

However,therealimplementation,besidescancellation,correctnesschecks,andexceptionhandling,isstillverydifferent.Thisisduetocodeperformanceoptimization.Theusualtaskscheduleriswrittenassumingthatwedonotknowhowmanytaskswearegoingtorun.Inthisspecificcase,thisisadefinedvalue.IfitislessthanorequaltoSMALL_ACTIONCOUNT_LIMIT(thatis10inthecurrent.NETFrameworkversion4.5),thenthealgorithmissimilartoourimplementation.

Inthecaseofmoretasks,itbecomesmorecomplicated.First,wecreateanemptyspecialtaskcalledreplicabletask.Thistaskistreatedinaspecialwaybyataskscheduler.Theimplementationcodeisasfollows:

varactionIndex=0;

varrootTask=

newReplicableTask(

()=>

{

intmyIndex;

www.EBooksWorld.ir

Page 157: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

while((myIndex=InterLocked.Increment(refactionIndex))<=

actions.Length)

body(myIndex-1);

});

rootTask.RunSynchronously();

rootTask.Wait();

Here,wehavetheactionIndexlocalvariablethatisusedbythetaskcodeinsidethelambdaexpression.Thiscreatesaclosure,andtheC#compilergeneratesahelperclassinstanceandputstheactionIndexvariableinsidethisclassasafield.Thus,ifwecreatemorecopiesofthistask,theyallwillshareasingleactionIndexvariable.Atthesametime,themyIndexvariablewillbedifferentforeachcopyofthistask.

Soaschedulingalgorithmcancreateasmanycopiesofthistaskasneeded,andstillitisguaranteedthateveryactionwillbeexecutedatleastonceoronlyonetime.Thisallowsschedulingmechanismstoworkefficiently.First,wecreateasmanycopiesofthethreadsastheCPUsupport.Then,iftasksrunlongerthanacertainamountoftime,theschedulerwillcreatemorecopiestopreventCPUcoresfromidling.Thismakesthetasksrunslightlyslower,butweknowthatourtasksarelong-runningandthiswillnotbeimportantforoverallperformance.

Thisalgorithmalsoensuresthat,evenwhenwehavemanyactionstoberun,therealnumberoftasksthataretobeexecutedinparallelwillbelowandclosetothenumberofthreadsthattheCPUsupports.

www.EBooksWorld.ir

Page 158: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Parallel.ForandParallel.ForeachThesemethodsareusefultocreateparallelloops.TheyusethesamestrategyasParallel.Invoke,sinceitisveryeffectivewhenhavingalargenumberofiterationstoruninparallel.Parallel.Foreachoffersevenmorecontrol,allowingustouseacustomtaskpartitioningalgorithmwiththePartitioner<T>andOrderablePartitioner<T>abstractclassimplementations.

Toseethedefaultparallelizationstrategy,let’srunthiscode:

privatestaticvoidCalc(intiterations)

{

vartaskIds=newHashSet<int>();

varsum=0;

Parallel.For(

0,

iterations,

i=>

{

Thread.SpinWait(1000000);

lock(taskIds)

taskIds.Add(Task.CurrentId.Value);

});

Console.WriteLine("{0}iterations,{1}tasks",iterations,

taskIds.Count);

}

WesimplycalltheParallel.Formethodwithadifferentnumberofiterationsandcounthowmanyuniquetaskidswe’vegot.

OnamachinewithCorei7-2600KCPU,wewillgetthesevalues:

1iteration,1tasks

4iterations,4tasks

8iterations,8tasks

12iterations,8tasks

16iterations,8tasks

32iterations,9tasks

64iterations,9tasks

TheCPUsupportseightconcurrentthreads,andthealgorithmchoseeighttaskstoruninparalleluntil32iterations,whenoneadditionaltaskisaddedtopreventpossibleCPUidling;thismakesthecodemoreefficient.

www.EBooksWorld.ir

Page 159: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UnderstandingthetaskschedulerThetaskschedulermanagesandexecutesTPLtasks.First,wewillreviewadefaulttaskscheduleralgorithm,andthenwewilllearnhowtocreateacustomtaskscheduleranduseitwithTPLtoruntasksonit.

Thedefaulttaskschedulerisbasedonthe.NETthreadpoolandusesitsglobalqueuetoruntop-leveltasksthatarenotcreatedinthecontextofanothertask.However,ifwecreateanestedorchildtask,itisputonalocalqueuethatiscreatedonaworkerthreadthatrunstheparenttask.Whenthisworkerthreadgetsreadytorunatask,itfirstlooksforworkitemsonthelocalqueuethatisaccessedinLIFOorder.Usinglocalqueuereducescontentionsincewedonotaccessanyshareddata;thus,thereisnoneedforanysynchronization.

Ifthelocalqueueisempty,theworkerthreadlooksintoaglobalqueue.Ifthisqueueisempty,thentopreventidlingthethreadisgoingtolookatotherthreads’localqueues.Ifthethreadfindsaworkitemhereafterrunningsomeheuristicstodecideiftakingthisworkitemwillbeefficient,thethreadstealsthisworkitemfromanotherthread’slocalqueue.ThestealinghappensinFIFOorderforefficiencyreasons.

ThiswayTPLtriestoimproveperformancebyloweringcontentionandusingCPUcachemoreeffectively,andatthesametime,byloadbalancingbetweenworkerthreadswithawork-stealingalgorithm.

Thedefaultschedulerworkswell,butinsomecases,weneedtoreplaceitwithanother.ImagineaWPFapplicationthathasabuttonclickedeventhandlerwiththefollowingcode:

vart=Task.Factory.StartNew(()=>

{

Console.WriteLine("Id:{0},Isthreadpoolthread:{1}",

Thread.CurrentThread.ManagedThreadId,

Thread.CurrentThread.IsThreadPoolThread);

Thread.Sleep(TimeSpan.FromSeconds(1));

_label.Content=newTextBlock{Text="HellofromTPLtask"};

},

CancellationToken.None,

TaskCreationOptions.None,

TaskScheduler.Default);

while(t.Status!=TaskStatus.RanToCompletion&&t.Status!=

TaskStatus.Faulted)

{

//runmessageloop

Application.Current.Dispatcher.Invoke(

DispatcherPriority.Background,newAction(delegate{}));

}

if(null!=t.Exception)

{

varinnerException=t.Exception.Flatten().InnerException;

Console.WriteLine("{0}:{1}",innerException.GetType(),

www.EBooksWorld.ir

Page 160: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

innerException.Message);

}

Ifwerunthiscode,wewillseethefollowing:

Id:4,Isthreadpoolthread:True

System.InvalidOperationException:ThecallingthreadmustbeSTA,because

manyUIcomponentsrequirethis.

ThereasonisthatwetriedtoaccesstheUIcontrolfromathreadpoolworkerthread,whichisforbidden.Tomakethiscodework,wehavetouseataskschedulerthatwillputthistaskonaUIthread:

vart=Task.Factory.StartNew(()=>

{

Console.WriteLine("Id:{0},Isthreadpoolthread:{1}",

Thread.CurrentThread.ManagedThreadId,

Thread.CurrentThread.IsThreadPoolThread);

Thread.Sleep(TimeSpan.FromSeconds(1));

_label.Content=newTextBlock{Text="HellofromTPLtask"};

},

CancellationToken.None,

TaskCreationOptions.None,

TaskScheduler.FromCurrentSynchronizationContext());

Theoutputwillbedifferent,andtheprogramwillrunsuccessfully,changingthelabelvalue:

Id:1,Isthreadpoolthread:False

TheUIandasynchronyisaverylargeandcomplicatedtopic.Wewillgetbacktothislaterinthisbook.

Lastbutnottheleastisimplementingacustomtaskscheduler.WeneedtoinheritthisfromtheTaskSchedulerclassandimplementseveralabstractmembers:

publicclassSynchronousTaskScheduler:TaskScheduler

{

//wedonotscheduletasks,werunthemsynchronously

protectedoverrideIEnumerable<Task>GetScheduledTasks()

{

returnEnumerable.Empty<Task>();

}

//runthetasksynchronouslyonthecurrentthread

protectedoverridevoidQueueTask(Tasktask)

{

TryExecuteTask(task);

}

//thesamething–justrunthetaskoncurrentthread

protectedoverrideboolTryExecuteTaskInline(

Tasktask,booltaskWasPreviouslyQueued)

{

returnTryExecuteTask(task);

}

www.EBooksWorld.ir

Page 161: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

//maximumconcurrencylevelis1,becauseonlyonetaskrunsat//atime

publicoverrideintMaximumConcurrencyLevel

{

get{return1;}

}

}

Ofcourse,real-worldtaskschedulersaremuchmorecomplicatedthanthisone,butthisworkstoo.Let’susethiswiththepreviouscode:

vart=Task.Factory.StartNew(()=>

{

Console.WriteLine("Id:{0},Isthreadpoolthread:{1}",

Thread.CurrentThread.ManagedThreadId,

Thread.CurrentThread.IsThreadPoolThread);

Thread.Sleep(TimeSpan.FromSeconds(1));

_label.Content=newTextBlock{Text="HellofromTPLtask"};

},

CancellationToken.None,

TaskCreationOptions.None,

newSynchronousTaskScheduler());

Thecodewillworkfineandwewillgetthesameresults.

www.EBooksWorld.ir

Page 162: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 163: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,wehavereviewedTaskParallelLibraryindetail.Wehavestudieditsarchitectureandcompositionblocks.Wehavelearnedaboutexceptionhandlingandtaskcancellationindetail.Weexaminedperformanceandlatencyissuesbyfindingoutthebestwayofwritingcodetoachievegoodresults.UsingtheParallelclassAPIallowedustoquicklycreateparallelprograms,anddeep-divingintoTPLtaskschedulingallowedustowriteacustomtaskschedulerandcustomizeTPLtaskexecution.

Inthenextchapter,wewilllearnhowtheC#languagesupportsasynchrony.Wewillunderstanditsnewkeywords,asyncandawait,andunderstandhowwecanuseTaskParallelLibrarywiththenewC#syntax.Also,wewillreviewindetailhowexactlynewlanguagefeaturesworkandcreateourowncustomcodethatwillbecompatiblewiththeawaitstatement.

www.EBooksWorld.ir

Page 164: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 165: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter5.C#LanguageSupportforAsynchronyTheTaskParallelLibrarymakesitpossibletocombineasynchronoustasksandsetdependenciesbetweenthem.Inthepreviouschapter,wereviewedthistopicindetail.Howevertogetaclearunderstandinginthischapter,wewillusethisapproachtosolvearealproblem—downloadingimagesfromBing(thesearchengine).Also,wewilldothefollowing:

ImplementstandardsynchronousapproachUseTaskParallelLibrarytocreateanasynchronousversionoftheprogramUseC#5.0built-inasynchronysupporttomakethecodeeasiertoreadandmaintainSimulateC#asynchronousinfrastructurewiththehelpofiteratorsLearnaboutotherusefulfeaturesofTaskParallelLibraryMakeanyC#typecompatiblewithbuilt-inasynchronouskeywords

www.EBooksWorld.ir

Page 166: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ImplementingthedownloadingofimagesfromBingEverydayBing.compublishesitsbackgroundimagethatcanbeusedasdesktopwallpaper.ThereisanXMLAPItogetinformationaboutthesepicturesthatcanbefoundathttp://www.bing.com/hpimagearchive.aspx.

www.EBooksWorld.ir

Page 167: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CreatingasimplesynchronoussolutionLet’strytowriteaprogramtodownloadthelasteightimagesfromthissite.Wewillstartbydefiningobjectstostoreimageinformation.Thisiswhereathumbnailimageanditsdescriptionwillbestored:

usingSystem.Drawing;

publicclassWallpaperInfo

{

privatereadonlyImage_thumbnail;

privatereadonlystring_description;

publicWallpaperInfo(Imagethumbnail,stringdescription)

{

_thumbnail=thumbnail;

_description=description;

}

publicImageThumbnail

{

get{return_thumbnail;}

}

publicstringDescription

{

get{return_description;}

}

}

Thenextcontainertypeisforallthedownloadedpicturesandthetimerequiredtodownloadandmakethethumbnailimagesfromtheoriginalpictures:

publicclassWallpapersInfo

{

privatereadonlylong_milliseconds;

privatereadonlyWallpaperInfo[]_wallpapers;

publicWallpapersInfo(longmilliseconds,WallpaperInfo[]wallpapers)

{

_milliseconds=milliseconds;

_wallpapers=wallpapers;

}

publiclongMilliseconds

{

get{return_milliseconds;}

}

publicWallpaperInfo[]Wallpapers

{

get{return_wallpapers;}

}

}

NowweneedtocreatealoaderclasstodownloadimagesfromBing.Weneedtodefineawww.EBooksWorld.ir

Page 168: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Loaderstaticclassandfollowwithanimplementation.Let’screateamethodthatwillmakeathumbnailimagefromthesourceimagestream:

privatestaticImageGetThumbnail(StreamimageStream)

{

using(imageStream)

{

varfullBitmap=Image.FromStream(imageStream);

returnnewBitmap(fullBitmap,192,108);

}

}

TocommunicateviatheHTTPprotocol,itisrecommendedtousetheSystem.Net.HttpClienttypefromtheSystem.Net.dllassembly.Let’screatethefollowingextensionmethodsthatwillallowustousethePOSTHTTPmethodtodownloadanimageandgetanopenedstream:

privatestaticStreamDownloadData(thisHttpClientclient,stringuri)

{

varresponse=client.PostAsync(

uri,newStringContent(string.Empty)).Result;

returnresponse.Content.ReadAsStreamAsync().Result;

}

privatestaticTask<Stream>DownloadDataAsync(thisHttpClientclient,

stringuri){

Task<HttpResponseMessage>responseTask=client.PostAsync(uri,new

StringContent(string.Empty));

returnresponseTask.ContinueWith(task=>

task.Result.Content.ReadAsStreamAsync()).Unwrap();

}

Tocreatetheeasiestimplementationpossible,wewillimplementdownloadingwithoutanyasynchrony.Here,wewilldefineHTTPendpointsfortheBingAPI:

privateconststring_catalogUri=

"http://www.bing.com/hpimagearchive.aspx?format=xml&idx=0&n=8&mbl=1&mkt=en-

ww";

privateconststring_imageUri="http://bing.com{0}_1920x1080.jpg";

Then,wewillstartmeasuringthetimerequiredtofinishdownloadinganddownloadanXMLcatalogthathasinformationabouttheimagesthatweneed:

varsw=Stopwatch.StartNew();

varclient=newHttpClient();

varcatalogXmlString=client.DownloadString(_catalogUri);

Next,theXMLstringwillbeparsedtoanXMLdocument:

varxDoc=XDocument.Parse(catalogXmlString);

NowusingLINQtoXML,wewillquerytheinformationneededfromthedocumentandrunthedownloadprocessforeachimage:

www.EBooksWorld.ir

Page 169: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

varwallpapers=xDoc

.Root

.Elements("image")

.Select(e=>

new

{

Desc=e.Element("copyright").Value,

Url=e.Element("urlBase").Value

})

.Select(item=>

new

{

item.Desc,

FullImageData=client.DownloadData(

string.Format(_imageUri,item.Url))

})

.Select(item=>

newWallpaperInfo(

GetThumbnail(item.FullImageData),

item.Desc))

.ToArray();

sw.Stop();

ThefirstSelectmethodcallextractstheimageURLanddescriptionfromeachimageXMLelementthatisadirectchildofrootelement.ThisinformationiscontainedinsidetheurlBaseandcopyrightXMLelementsinsidetheimageelement.ThesecondonedownloadsanimagefromtheBingsite.ThelastSelectmethodcreatesathumbnailimageandstoresalltheinformationneededinsidetheWallPaperInfoclassinstance.

Todisplaytheresults,weneedtocreateauserinterface.WindowsFormsisasimpleandfastwaytoimplementthetechnology,sowecanuseittoshowtheresultstotheuser.Thereisabuttonthatrunsthedownload,apaneltoshowthedownloadedpictures,andalabelthatwillshowthetimerequiredtofinishdownloading.

Hereistheimplementationcode.Thisincludesacalculationofthetopco-ordinateforeachelement,acodetodisplaytheimagesandstartthedownloadprocess:

privateintGetItemTop(intheight,intindex)

{

returnindex*(height+8)+8;

}

privatevoidRefreshContent(WallpapersInfoinfo)

{

_resultPanel.Controls.Clear();

_resultPanel.Controls.AddRange(

info.Wallpapers.SelectMany((wallpaper,i)=>newControl[]

{

newPictureBox

{

Left=4,

Image=wallpaper.Thumbnail,

AutoSize=true,

Top=GetItemTop(wallpaper.Thumbnail.Height,i)

www.EBooksWorld.ir

Page 170: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

},

newLabel

{

Left=wallpaper.Thumbnail.Width+8,

Top=GetItemTop(wallpaper.Thumbnail.Height,i),

Text=wallpaper.Description,

AutoSize=true

}

}).ToArray());

_timeLabel.Text=string.Format(

"Time:{0}ms",info.Milliseconds);

}

privatevoid_loadSyncBtn_Click(objectsender,System.EventArgse)

{

varinfo=Loader.SyncLoad();

RefreshContent(info);

}

Theresultlooksasfollows:

www.EBooksWorld.ir

Page 171: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SothetimetodownloadalltheseimagesshouldbeaboutseveralsecondsiftheInternetconnectionisbroadband.Canwedothisfaster?Wecertainlycan!Nowwewilldownloadandprocesstheimagesonebyone,butwetotallycanprocesseachimageinparallel.

www.EBooksWorld.ir

Page 172: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CreatingaparallelsolutionwithTaskParallelLibraryInthepreviouschapter,wereviewedTaskParallelLibraryandtherelationshipsbetweentasks.Thecodenaturallysplitsintoseveralstages:

LoadimagescatalogXMLfromBingParsetheXMLdocumentandgettheinformationneededabouttheimagesLoadeachimage’sdatafromBingCreateathumbnailimageforeachimagedownloaded

Theprocesscanbevisualizedwiththedependencychart:

HttpClienthasnaturallyasynchronousAPI,soweonlyneedtocombineeverythingtogetherwiththehelpofaTask.ContinueWithmethod:

publicstaticTask<WallpapersInfo>TaskLoad()

{

varsw=Stopwatch.StartNew();

vardownloadBingXmlTask=newHttpClient().GetStringAsync(

_catalogUri);

varparseXmlTask=downloadBingXmlTask.ContinueWith(task=>

{

varxmlDocument=XDocument.Parse(task.Result);

returnxmlDocument.Root

.Elements("image")

.Select(e=>

new

{

Description=e.Element("copyright").Value,

Url=e.Element("urlBase").Value

www.EBooksWorld.ir

Page 173: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

});

});

vardownloadImagesTask=parseXmlTask.ContinueWith(

task=>Task.WhenAll(

task.Result.Select(item=>newHttpClient()

.DownloadDataAsync(string.Format(_imageUri,item.Url))

.ContinueWith(downloadTask=>newWallpaperInfo(

GetThumbnail(downloadTask.Result),item.Description)))))

.Unwrap();

returndownloadImagesTask.ContinueWith(task=>

{

sw.Stop();

returnnewWallpapersInfo(sw.ElapsedMilliseconds,task.Result);

});

}

Thecodehassomeinterestingmoments.ThefirsttaskiscreatedbytheHttpClientinstance,anditcompleteswhenthedownloadprocesssucceeds.Nowwewillattachasubsequenttask,whichwillusetheXMLstringdownloadedbytheprevioustask,andthenwewillcreateanXMLdocumentfromthisstringandextracttheinformationneeded.

Nowthisisbecomingmorecomplicated.Wewanttocreateatasktodownloadeachimageandcontinueuntilallthesetaskscompletesuccessfully.SowewillusetheLINQSelectmethodtorundownloadsforeachimagethatwasdefinedintheXMLcatalog,andafterthedownloadprocesscompletes,wewillcreateathumbnailimageandstoretheinformationintheWallpaperInfoinstance.ThiscreatesIEnumerable<Task<WallpaperInfo>>asaresult,andtowaitforallthesetaskstocomplete,wewillusetheTask.WhenAllmethod.However,thisisataskthatisinsideacontinuationtask,andtheresultisgoingtobeoftheTask<Task<WallpaperInfo[]>>type.Togettheinnertask,wewillusetheUnwrapmethod,whichhasthefollowingsyntax:

publicstaticTaskUnwrap(thisTask<Task>task)

ThiscanbeusedonanyTask<Task>instanceandwillcreateaproxytaskthatrepresentsanentireasynchronousoperationproperly.

Thelasttaskistostopthetimerandreturnthedownloadedimagesandisquitestraightforward.WehavetoaddanotherbuttontotheUItorunthisimplementation.Noticetheimplementationofthebuttonclickhandler:

privatevoid_loadTaskBtn_Click(objectsender,System.EventArgse)

{

varinfo=Loader.TaskLoad();

info.ContinueWith(task=>RefreshContent(task.Result),

CancellationToken.None,

TaskContinuationOptions.None,

TaskScheduler.FromCurrentSynchronizationContext());

}

SincetheTaskLoadmethodisasynchronous,itreturnsimmediately.Todisplaytheresults,

www.EBooksWorld.ir

Page 174: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

wehavetodefineacontinuationtask.However,fromthepreviouschapteryoualreadyknowthatthedefaulttaskschedulerwillrunataskcodeonathreadpoolworkerthread.ToworkwithUIcontrols,wehavetorunthecodeontheUIthread,andweuseataskschedulerthatcapturesthecurrentsynchronizationcontextandrunsthecontinuationtaskonthis.WewillcoversynchronizationcontextandtherelatedinfrastructurelaterinChapter8,Server-SideAsynchrony,andChapter9,ConcurrencyintheUserInterface,whereserver-sideandclient-sideasynchronywillbereviewedindetail.

Let’snamethebuttonasLoadusingTPLandtesttheresults.IfyourInternetconnectionisfast,thisimplementationwilldownloadtheimagesinparallelmuchfastercomparedtotheprevioussequentialdownloadprocess.

Ifwelookbackatthecode,wewillseethatitisquitehardtounderstandwhatitactuallydoes.Wecanseehowonetaskdependsontheother,buttheoriginalgoalisuncleardespitethecodebeingverycompactandeasy.Imaginewhatwillhappenifwetrytoaddexceptionhandlinghere.Wewouldhavetoappendanadditionalcontinuationtaskwithexceptionhandlingtoeachtask.Thiswillbemuchhardertoreadandunderstand.Inareal-worldprogram,itwillbeachallengingtasktokeepinmindthesetaskscompositionandsupportacodewritteninsuchaparadigm.

www.EBooksWorld.ir

Page 175: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

EnhancingthecodewithC#5.0built-insupportforasynchronyFortunately,C#5.0introducedtheasyncandawaitkeywordsthatareintendedtomakeasynchronouscodelooksynchronous,andthus,makesreadingofcodeandunderstandingtheprogramfloweasier.However,thisisanotherabstractionandithidesmanythingsthathappenunderthehoodfromtheprogrammer,whichinseveralsituationsisnotagoodthing.Thepotentialpitfallsandsolutionswillbecoveredlaterinthisbook,butnowlet’srewritethepreviouscodeusingnewC#5.0features:

publicstaticasyncTask<WallpapersInfo>AsyncLoad()

{

varsw=Stopwatch.StartNew();

varclient=newHttpClient();

varcatalogXmlString=awaitclient.GetStringAsync(_catalogUri);

varxDoc=XDocument.Parse(catalogXmlString);

varwallpapersTask=xDoc

.Root

.Elements("image")

.Select(e=>

new

{

Description=e.Element("copyright").Value,

Url=e.Element("urlBase").Value

})

.Select(asyncitem=>

new

{

item.Description,

FullImageData=awaitclient.DownloadDataAsync(

string.Format(_imageUri,item.Url))

});

varwallpapersItems=awaitTask.WhenAll(wallpapersTask);

varwallpapers=wallpapersItems.Select(

item=>newWallpaperInfo(

GetThumbnail(item.FullImageData),item.Description));

sw.Stop();

returnnewWallpapersInfo(sw.ElapsedMilliseconds,

wallpapers.ToArray());

}

Nowthecodelooksalmostlikethefirstsynchronousimplementation.TheAsyncLoadmethodhasaasyncmodifierandaTask<T>returnvalue,andsuchmethodsmustalwaysreturnTaskorbedeclaredasvoid—thisisenforcedbythecompiler.However,inthemethod’scode,thetypethatisreturnedisjustT.Thisisstrangeatfirst,butthemethod’sreturnvaluewillbeeventuallyturnedintoTask<T>bytheC#5.0compiler.Theasyncmodifierisnecessarytouseawaitinsidethemethod.Inthefurthercode,thereisawait

www.EBooksWorld.ir

Page 176: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

insidealambdaexpression,andweneedtomarkthislambdaasasyncaswell.

Sowhatisgoingonwhenweuseawaitinsideourcode?Itdoesnotalwaysmeanthatthecallisactuallyasynchronous.Itcanhappenthatbythetimewecallthemethod,theresultisalreadyavailable,sowejustgettheresultandproceedfurther.However,themostcommoncaseiswhenwemakeanasynchronouscall.Inthiscase,westart,forexample,bydownloadingaXMLstringfromBingviaHTTPandimmediatelyreturnataskthatisacontinuationtaskandcontainstherestofthecodeafterthelinewithawait.

Torunthis,weneedtoaddanotherbuttonnamedLoadusingasync.Wearegoingtouseawaitinthebuttonclickeventhandleraswell,soweneedtomarkitwiththeasyncmodifier:

privateasyncvoid_loadAsyncBtn_Click(objectsender,System.EventArgse)

{

varinfo=awaitLoader.AsyncLoad();

RefreshContent(info);

}

Nowifthecodeafterawaitisbeingruninacontinuationtask,whyistherenomultithreadedaccessexception?TheRefreshContentmethodrunsinanothertask,buttheC#compilerisawareofthesynchronizationcontextandgeneratesacodethatexecutesthecontinuationtaskontheUIthread.TheresultshouldbeasfastasaTPLimplementationbutthecodeismuchcleanerandeasytofollow.

Lastbutnotleast,isthepossibilitytoputasynchronousmethodcallsinsideatryblock.TheC#compilergeneratesacodethatwillpropagatetheexceptionintothecurrentcontextandunwraptheAggregateExceptioninstancetogettheoriginalexceptionfromit.

NoteInC#5.0,itwasimpossibletouseawaitinsidecatchandfinallyblocks,butC#6.0introducedanewasync/awaitinfrastructureandthislimitationwasremoved.

www.EBooksWorld.ir

Page 177: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SimulatingC#asynchronousinfrastructurewithiteratorsTodigintotheimplementationdetails,itmakessensetolookatthedecompiledcodeoftheAsyncLoadmethod:

publicstaticTask<WallpapersInfo>AsyncLoad()

{

Loader.<AsyncLoad>d__21stateMachine;

stateMachine.<>t__builder=

AsyncTaskMethodBuilder<WallpapersInfo>.Create();

stateMachine.<>1__state=-1;

stateMachine

.<>t__builder

.Start<Loader.<AsyncLoad>d__21>(refstateMachine);

returnstateMachine.<>t__builder.Task;

}

Themethodbodywasreplacedbyacompiler-generatedcodethatcreatesaspecialkindofstatemachine.Wewillnotreviewthefurtherimplementationdetailshere,becauseitisquitecomplicatedandissubjecttochangesfromversiontoversion.However,what’sgoingonisthatthecodegetsdividedintoseparatepiecesateachlinewhereawaitispresent,andeachpiecebecomesaseparatestateinthegeneratedstatemachine.Then,aspecialSystem.Runtime.CompilerServices.AsyncTaskMethodBuilderstructurecreatesTaskthatrepresentsthegeneratedstatemachineworkflow.

Thisstatemachineisquitesimilartotheonethatisgeneratedfortheiteratormethodsthatleveragetheyieldkeyword.InC#6.0,thesameuniversalcodegetsgeneratedforthecodecontainingyieldandawait.Toillustratethegeneralprinciplesbehindthegeneratedcode,wecanuseiteratormethodstoimplementanotherversionofasynchronousimagesdownloadfromBing.

Therefore,wecanturnanasynchronousmethodintoaniteratormethodthatreturnstheIEnumerable<Task>instance.WereplaceeachawaitwithyieldreturnmakingeachiterationtobereturnedasTask.Torunsuchamethod,weneedtoexecuteeachtaskandreturnthefinalresult.ThiscodecanbeconsideredasananalogueofAsyncTaskMethodBuilder:

privatestaticTask<TResult>ExecuteIterator<TResult>(

Func<Action<TResult>,IEnumerable<Task>>iteratorGetter)

{

returnTask.Run(()=>

{

varresult=default(TResult);

foreach(vartaskiniteratorGetter(res=>result=res))

task.Wait();

returnresult;

});

www.EBooksWorld.ir

Page 178: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

}

Weiteratethrougheachtaskandawaititscompletion.Sincewecannotusetheoutandrefparametersiniteratormethods,weusealambdaexpressiontoreturntheresultfromeachtask.Tomakethecodeeasiertounderstand,wehavecreatedanewcontainertaskandusedtheforeachloop;however,tobeclosertotheoriginalimplementation,weshouldgetthefirsttaskandusetheContinueWithmethodprovidingthenexttasktoitandcontinueuntilthelasttask.Inthiscase,wewillenduphavingonefinaltaskrepresentinganentiresequenceofasynchronousoperations,butthecodewillbecomemorecomplicatedaswell.

SinceitisnotpossibletousetheyieldkeywordinsidealambdaexpressionsinthecurrentC#versions,wewillimplementimagedownloadandthumbnailgenerationasaseparatemethod:

privatestaticIEnumerable<Task>GetImageIterator(

stringurl,

stringdesc,

Action<WallpaperInfo>resultSetter)

{

varloadTask=newHttpClient().DownloadDataAsync(

string.Format(_imageUri,url));

yieldreturnloadTask;

varthumbTask=Task.FromResult(GetThumbnail(loadTask.Result));

yieldreturnthumbTask;

resultSetter(newWallpaperInfo(thumbTask.Result,desc));

}

ItlookslikeacommonC#asynccodewithyieldreturnusedinsteadoftheawaitkeywordandresultSetterusedinsteadofreturn.NoticetheTask.FromResultmethodthatweusedtogetTaskfromthesynchronousGetThumbnailmethod.WecanuseTask.Runandputthisoperationonaseparateworkerthread,butitwillbeanineffectivesolution.Task.FromResultallowsustogetTaskthatisalreadycompletedandhasaresult.Ifyouuseawaitwithsuchtask,itwillbetranslatedintoasynchronouscall.

Themaincodecanberewritteninthesameway:

privatestaticIEnumerable<Task>GetWallpapersIterator(

Action<WallpaperInfo[]>resultSetter)

{

varcatalogTask=newHttpClient().GetStringAsync(_catalogUri);

yieldreturncatalogTask;

varxDoc=XDocument.Parse(catalogTask.Result);

varimagesTask=Task.WhenAll(xDoc

.Root

.Elements("image")

.Select(e=>new

{

Description=e.Element("copyright").Value,

www.EBooksWorld.ir

Page 179: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Url=e.Element("urlBase").Value

})

.Select(item=>ExecuteIterator<WallpaperInfo>(

resSetter=>GetImageIterator(

item.Url,item.Description,resSetter))));

yieldreturnimagesTask;

resultSetter(imagesTask.Result);

}

Thiscombineseverythingtogether:

publicstaticWallpapersInfoIteratorLoad()

{

varsw=Stopwatch.StartNew();

varwallpapers=ExecuteIterator<WallpaperInfo[]>(GetWallpapersIterator)

.Result;

sw.Stop();

returnnewWallpapersInfo(sw.ElapsedMilliseconds,wallpapers);

}

Torunthis,wewillcreateonemorebuttoncalledLoadusingiterator.ThebuttonclickhandlerjustrunstheIteratorLoadmethodandthenrefreshestheUI.Thisalsoworkswithaboutthesamespeedasotherasynchronousimplementations.

ThisexamplecanhelpustounderstandthelogicbehindtheC#codegenerationforasynchronousmethodsusedwithawait.Ofcourse,therealcodeismuchmorecomplicated,buttheprinciplesbehinditremainthesame.

www.EBooksWorld.ir

Page 180: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 181: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Istheasynckeywordreallyneeded?Itisacommonquestionaboutwhydoweneedtomarkmethodsasasync.WehavealreadymentionediteratormethodsinC#andtheyieldkeyword.Thisisverysimilartoasync/await,andyetwedonotneedtomarkiteratormethodswithanymodifier.TheC#compilerisabletodeterminethatitisaniteratormethodwhenitmeetstheyieldreturnoryieldbreakoperatorsinsidesuchamethod.Sothequestionis,whyisitnotthesamewithawaitandtheasynchronousmethods?

ThereasonisthatasynchronysupportwasintroducedinthelatestC#version,anditisveryimportantnottobreakanylegacycodewhilechangingthelanguage.Imagineifanycodeusedawaitasanameforafieldorvariable.IfC#developersmakeawaitakeywordwithoutanyconditions,thisoldcodewillbreakandstopcompiling.Thecurrentapproachguaranteesthatifwedonotmarkamethodwithasync,theoldcodewillcontinuetowork.

www.EBooksWorld.ir

Page 182: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 183: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Fire-and-forgettasksBesidesTaskandTask<T>,wecandeclareanasynchronousmethodasvoid.Itisusefulinthecaseoftop-leveleventhandlers,forexample,thebuttonclickortextchangedhandlersintheUI.Aneventhandlerthatreturnsavalueispossible,butisveryinconvenienttouseanddoesnotmakemuchsense.

Soallowingasyncvoidmethodsmakesitpossibletouseawaitinsidesucheventhandlers:

privateasyncvoidbutton1_Click(objectsender,EventArgse)

{

awaitSomeAsyncStuff();

}

Itseemsthatnothingbadishappening,andtheC#compilergeneratesalmostthesamecodeasfortheTaskreturningmethod,butthereisanimportantcatchrelatedtoexceptionshandling.

WhenanasynchronousmethodreturnsTask,exceptionsareconnectedtothistaskandcanbehandledbothbyTPLandthetry/catchblockincaseawaitisused.However,ifwehaveaasyncvoidmethod,wehavenoTasktoattachtheexceptionstoandthoseexceptionsjustgetpostedtothecurrentsynchronizationcontext.TheseexceptionscanbeobservedusingAppDomain.UnhandledExceptionorsimilareventsinaGUIapplication,butthisisveryeasytomissandnotagoodpractice.

Theotherproblemisthatwecannotuseavoidreturningasynchronousmethodwithawait,sincethereisnoreturnvaluethatcanbeusedtoawaitonit.Wecannotcomposesuchamethodwithotherasynchronoustasksandparticipateintheprogramworkflow.Itisbasicallyafire-and-forgetoperationthatwestart,andthenwehavenowaytocontrolhowitwillproceed(ifwedidnotwritethecodeforthisexplicitly).

Anotherproblemisvoidreturningasynclambdaexpression.Itisveryhardtonoticethatlambdareturnsvoid,andallproblemsrelatedtousualmethodsarerelatedtolambdaexpressionaswell.Imaginethatwewanttorunsomeoperationinparallel.Fromthepreviouschapterwelearnedthattoachievethis,wecanusetheParallel.ForEachmethod.Todownloadsomenewsinparallel,wecanwriteacodelikethis:

Parallel.ForEach(Enumerable.Range(1,10),asynci=>

{

varnews=awaitnewsClient.GetTopNews(i);

newsCollection.Add(news);

});

However,thiswillnotwork,becausethesecondparameteroftheForEachmethodisAction<T>,whichisavoidreturningdelegate.Thus,wewillspawn10downloadprocesses,butsincewecannotwaitforcompletion,weabandonallasynchronousoperationsthatwejuststartedandignoretheresults.

Ageneralruleofthumbistoavoidusingasyncvoidmethods.Ifthisisinevitableandthereisaneventhandler,thenalwayswraptheinnerawaitmethodcallsintry/catchblocksandprovideexceptionhandling.

www.EBooksWorld.ir

Page 184: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 185: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 186: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

OtherusefulTPLfeaturesTaskParallelLibraryhasalargecodebaseandsomeusefulfeaturessuchasTask.UnwraporTask.FromResultthatarenotverywellknowntodevelopers.Wehavestillnotmentionedtwomoreextremelyusefulmethodsyet.Theyarecoveredinthefollowingsections.

www.EBooksWorld.ir

Page 187: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Task.DelayOften,itisrequiredtowaitforacertainamountoftimeinthecode.OneofthetraditionalwaystowaitisusingtheThread.Sleepmethod.TheproblemisthatThread.Sleepblocksthecurrentthread,anditisnotasynchronous.

Anotherdisadvantageisthatwecannotcancelwaitingifsomethinghashappened.Toimplementasolutionforthis,wewillhavetousesystemsynchronizationprimitivessuchasanevent,butthisisnotveryeasytocode.Tokeepthecodesimple,wecanusetheTask.Delaymethod:

//Dosomething

awaitTask.Delay(1000);

//Dosomething

ThismethodcanbecanceledwithahelpoftheCancellationTokeninfrastructureandusessystemtimerunderthehood,sothiskindofwaitingistrulyasynchronous.

www.EBooksWorld.ir

Page 188: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Task.YieldSometimesweneedapartofthecodetobeguaranteedtorunasynchronously.Forexample,weneedtokeeptheUIresponsive,ormaybewewouldliketoimplementafine-grainedscenario.Anyway,aswealreadyknowthatusingawaitdoesnotmeanthatthecallwillbeasynchronous.Ifwewanttoreturncontrolimmediatelyandruntherestofthecodeasacontinuationtask,wecanusetheTask.Yieldmethod:

//Dosomething

awaitTask.Yield();

//Dosomething

Task.Yieldjustcausesacontinuationtobepostedonthecurrentsynchronizationcontext,orifthesynchronizationcontextisnotavailable,acontinuationwillbepostedonathreadpoolworkerthread.

www.EBooksWorld.ir

Page 189: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 190: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ImplementingacustomawaitabletypeUntilnowwehaveonlyusedTaskwiththeawaitoperator.However,itisnottheonlytypethatiscompatiblewithawait.Actually,theawaitoperatorcanbeusedwitheverytypethatcontainstheGetAwaitermethodwithnoparametersandthereturntypethatdoesthefollowing:

ImplementstheINotifyCompletioninterfaceContainstheIsCompletedbooleanpropertyHastheGetResultmethodwithnoparameters

Thismethodcanevenbeanextensionmethod,soitispossibletoextendtheexistingtypesandaddtheawaitcompatibilitytothem.Inthisexample,wewillcreatesuchamethodfortheUritype.ThismethodwilldownloadcontentasastringviaHTTPfromtheaddressprovidedintheUriinstance:

privatestaticTaskAwaiter<string>GetAwaiter(thisUriurl)

{

returnnewHttpClient().GetStringAsync(url).GetAwaiter();

}

varcontent=awaitnewUri("http://google.com");

Console.WriteLine(content.Substring(0,10));

Ifwerunthis,wewillseethefirst10charactersoftheGooglewebsitecontent.

Asyoumaynotice,hereweusedtheTasktypeindirectly,returningthealreadyprovidedawaitermethodfortheTasktype.Wecanimplementanawaitermethodmanuallyfromscratch,butitreallydoesnotmakeanysense.TounderstandhowthisworksitwillbeenoughtocreateacustomwrapperaroundanalreadyexistingTaskAwaiter:

structDownloadAwaiter:INotifyCompletion

{

privatereadonlyTaskAwaiter<string>_awaiter;

publicDownloadAwaiter(Uriuri)

{

Console.WriteLine("Startdownloadingfrom{0}",uri);

vartask=newHttpClient().GetStringAsync(uri);

_awaiter=task.GetAwaiter();

Task.GetAwaiter().OnCompleted(()=>Console.WriteLine("download

completed"));

}

publicboolIsCompleted

{

get{return_awaiter.IsCompleted;}

}

publicvoidOnCompleted(Actioncontinuation)

{

_awaiter.OnCompleted(continuation);

}

www.EBooksWorld.ir

Page 191: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

publicstringGetResult()

{

return_awaiter.GetResult();

}

}

Withthiscode,wehavecustomizedasynchronousexecutionthatprovidesdiagnosticinformationtotheconsole.TogetridofTaskAwaiter,itwillbeenoughtochangetheOnCompletedmethodwithcustomcodethatwillexecutesomeoperationandthenacontinuationprovidedinthismethod.

Tousethiscustomawaiter,weneedtochangeGetAwaiteraccordingly:

privatestaticDownloadAwaiterGetAwaiter(thisUriuri)

{

returnnewDownloadAwaiter(uri);

}

Ifwerunthis,wewillseeadditionalinformationontheconsole.Thiscanbeusefulfordiagnosticsanddebugging.

www.EBooksWorld.ir

Page 192: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 193: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,welookedattheC#languageinfrastructurethatsupportsasynchronouscalls.WecoveredthenewC#keywords,asyncandawait,andhowwecanuseTaskParallelLibrarywiththenewC#syntax.WelearnedhowC#generatescodeandcreatesastatemachinethatrepresentsanasynchronousoperation,andweimplementedananaloguesolutionwiththehelpofiteratormethodsandtheyieldkeyword.Besidesthis,westudiedadditionalTaskParallelLibraryfeaturesandlookedathowwecanuseawaitwithanycustomtype.

Inthenextchapter,wewilllearnaboutdatastructuresthatarebuiltforconcurrencyandcommonalgorithmsthatrelyonthem.

www.EBooksWorld.ir

Page 194: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 195: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter6.UsingConcurrentDataStructuresChoosinganappropriatedatastructureforyourconcurrentalgorithmisacrucialstep.Wehavealreadylearnedfromthepreviouschaptersthatitisnotusuallypossibletousejustany.NETobjectasashareddatainamultithreadedprogram.Wecanassumethatmostofthecommontypesin.NETareimplementedinsuchawaythattheirstaticmembersarethread-safe,whiletheirinstancemembersarenot.However,onlythoseobjectsthatarespecificallydesignedtobethread-safecanbeusedastheyareinamultithreadedenvironment.

Therefore,ifweneedmultiplethreadstoaddsomeitemtoacollection,wecannotjustcalltheAddmethodofasharedinstanceoftheList<T>type.Itwillleadtounpredictableresults,andmostprobablytheprogramwillendupthrowingaweirdexception.

Thus,inthissituation,therearetwogeneralwaystofollow:eitherweimplementsynchronizedaccesstothestandardcollectionourselveswiththehelpofexistingsynchronizationprimitives,orwecanuseexistingconcurrentcollectionsfromtheSystem.Collections.Concurrentnamespace.

Inthischapter,wearegoingtodigintothedetailsofusingdatastructuresinconcurrentapplicationsandreviewadvantagesanddisadvantagesofeachoption.

www.EBooksWorld.ir

Page 196: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

StandardcollectionsandsynchronizationprimitivesTohighlightwhatproblemscanappearwhenweusenonthreadsafecollectionsinaconcurrentprogram,let’swriteasimpleprogramthatwillusetheParallel.Foreachclasstocopyacollectionanddoubleitselements:

varsource=Enumerable.Range(1,42000).ToList();

vardestination=newList<int>();

Parallel.ForEach(source,n=>destination.Add(n*2));

Assert.AreEqual(source.Count,destination.Count);

Ifwerunthiscode,wewillalmostcertainlygettheAggregateExceptionexceptionwiththeArgumentExceptioninstancewrappedinsideit.

ThishappensbecausetheAddmethodoftheList<T>classisnotthreadsafe,andthereasonforthisliesintheimplementationdetails:

publicvoidAdd(Titem)

{

if(_size==_items.Length)EnsureCapacity(_size+1);

_items[_size++]=item;

_version++;

}

Incasetheconcurrentthreadsaccessthismethodwhenthe_size==items.Length–1conditionistrue,theArgumentExceptionexceptionwillalmostcertainlyoccur.Theimplementationwillcausethecollectiontohaveaninconsistentstate;araceconditionwillleadtheinnerarraynewsizetobelessthanneeded.

Toavoidaracecondition,wecanimplementsomesortofsynchronizationforsharedcollectionaccessusingthelockstatement:

objectsyncRoot=newobject();

varsource=Enumerable.Range(1,42000).ToList();

vardestination=newList<int>();

Parallel.ForEach(source,

n=>

{

lock(syncRoot)

{

destination.Add(n*2);

}

});

Assert.AreEqual(source.Count,destination.Count);

Thiscodewillrunwithouterrors.However,itsefficiencywillbelessthandoingthesamejobfromasinglethread.Insteadofdoingcalculations,athreadwillbewaitingforasharedresource(inthiscase,itisthedestinationvariable)access.Thissituationis

www.EBooksWorld.ir

Page 197: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

calledthreadcontention,anditcansignificantlydecreaseyourprogramperformance.

TousealltheavailableCPUcoreseffectively,wealwayshavetotrytoreducecontentionasmuchaspossible.Insomecases,itispossibletousespecialsynchronizationprimitivesorlock-freealgorithms,orusethreadlocalcomputations,whicharemergedattheendofparallelcalculationstogetthefinalresult.

www.EBooksWorld.ir

Page 198: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 199: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ImplementingacachewithReaderWriterLockSlimCachingisacommontechniquethatisbeingusedinmanyapplicationstoincreaseperformanceandefficiency.Usually,readingfromacacheoccursmoreoftenthanwritingoperation,andthenumberofcachereadersishigherthatthenumberofwriters.

Inthisparticularcase,thereisnosenseinusinganexclusivelockpreventingotherthreadsfromreadinganothercachevalue.Thereisabuilt-insynchronizationobjectthathasexactlythisbehavior,anditiscalledReaderWriterLockSlim.

NoteThereareseveralclassesinthe.NETFrameworkinsidetheSystem.Threadingnamespace,whosenamesendwithSlim.ItisusuallymoreefficientandlightweighttoimplementthecorrespondingclasseswithoutSlimattheendoftheirnames.Inmostcases,youshouldprefertheSlimversionsoveroriginalones,unlessyouare100%surewhyyouneednon-slimobjects.ThisruleworkswiththeReaderWriterLockandReaderWriterLockSlimclassesaswell—alwayspreferaSlimobject,becauseithasmajorefficiencyandcorrectiveimprovements.

Cachecanbeuseddifferentlyintheapplication,butthemostcommonapproachisusingcacheasidepattern.Theclientisunawareofcaching;ifthereisalong-runningoperationandnoresultofthisoperationcanbefoundinthecache,weperformtheoperationandsavetheresultintothecache.Ifthereisaresultinthecache,wedonotstartalong-runningoperationbutusethecachedvalueinstead.

Asimplecodeofacacheproviderthatcontainsonelong-runningoperationandimplementscacheasidepatternwilllookasfollows:

publicclassCustomProvider

{

privatereadonlyDictionary<string,OperationResult>_cache=newDictionary<string,OperationResult>();

privatereadonlyReaderWriterLockSlim_rwLockSlim=

newReaderWriterLockSlim();

publicOperationResultRunOperationOrGetFromCache(

stringoperationId)

{

_rwLockSlim.EnterReadLock();

try

{

OperationResultresult;

if(_cache.TryGetValue(operationId,outresult))

returnresult;

}

finally

{

_rwLockSlim.ExitReadLock();

}

www.EBooksWorld.ir

Page 200: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

_rwLockSlim.EnterWriteLock();

try

{

OperationResultresult;

if(_cache.TryGetValue(operationId,outresult))

returnresult;

result=RunLongRunningOperation(operationId);

_cache.Add(operationId,result);

returnresult;

}

finally

{

_rwLockSlim.ExitReadLock();

}

}

privateOperationResultRunLongRunningOperation(

stringoperationId)

{

//Runningreallong-runningoperation

//...

}

}

NoteItisveryimportanttoalwaysimplementacacheinvalidationstrategy,whichismissinginthisdemocodeasitisnotrelevanttothetopicofthechapter.However,inreal-worldscenarios,youhavetopayattentiontothistoavoidmemoryleaks.Thesimpleinvalidationstrategycanbesettingacacheitemlifetimeexplicitlyorusingweakreferencessothatgarbagecollectionwillinvalidatethecache.

ThissampledemonstratesaCustomProviderclass,whichcontainsonlyoneRunOperationOrGetFromCachepublicmethod.ThismethodacceptsanoperationidentifierandreturnstheoperationresultasanOperationResultobject.Toimplementcorrectcacheparallelreading,inthebeginningweacquireareaderlockandthencheckthatthereisaresultinthecache.Ifnot,weacquireawriterlockandthencheckthatthereisanoperationvalueinsidethecache,whichcanappearwhileweareacquiringthelock.Ifthereisstillnothinginthecache,wewillrunthelong-runningoperation,putitsresultintothecache,andreturnittotheclient.

Ifwedon’tperformthischeck,wecangetArgumentExceptionwhentryingtoaddanitemwiththesamekeytothedictionarytwice,andasaresultwedounnecessarywork.

TipHowever,asitusuallyhappensinconcurrentprogramming,thisapproachcanbenon-effectiveindifferentsituations.UsingReaderWriterLockSlimforimplementingdictionary-basedcachingalmostalwaysleadtoworseperformancethansimplyusingacommonstatement,lock(syncRoot).Theproblemisthatacquiringreaderlockisnotaveryfastoperation.AReaderWriterLockSlimobjecthastoensurethatacquiringawriter

www.EBooksWorld.ir

Page 201: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

lockisnotpossiblewhilebeinginsideareaderblock,andthisrequirestheuseofsomesynchronizationlogic,whichiscostly.Ifalongrunningoperationisreallylongrunning,thisoverheadisnotsignificant.However,inourcase,readingavaluefromDictionaryisaveryfastoperation,andinthissituation,lockingtheoverheadbecomesnoticeable.Sincealockstatementusesspin-waitoptimizationforshortrunningoperations,itwillbemoreeffectiveinthisparticularcase.

Theprevioustipworksforchoosingadatastructureaswell.Insimplecases,implementinggenerallockingovernonthreadsafeobjectcouldworkbetterthanaspecializeduniversalthreadsafedatastructure.However,whenconcurrentprogramlogicbecomesmorecomplicated,itisagoodideatogoforstandardconcurrentdatastructures.

www.EBooksWorld.ir

Page 202: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 203: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Concurrentcollectionsin.NETSincethefirst.NETFrameworkversion,mostofthecollectionsintheSystem.CollectionsnamespacecontainedtheSynchronizedfactorymethodthatcreatesathreadsafewrapperoverthecollectioninstance,whichensuresthreadsafety:

varsource=Enumerable.Range(1,42000).ToList();

vardestination=ArrayList.Synchronized(newList<int>());

Parallel.ForEach(source,

n=>

{

destination.Add(n);

});

Assert.AreEqual(source.Count,destination.Count);

Thesynchronizedcollectionwrappercanbeusedinaconcurrentenvironment,butitsefficiencyislow,sinceitusessimplelockingensuringexclusivecollectionaccessforeveryoperation.Thisapproachiscalledcoarse-grainedlockinganditisdescribedinChapter3,UnderstandingParallelismGranularity.Itdoesnotscalewellwithanincreaseinthenumberofclientsandtheamountofdatainsidethecollection.

Acomplicated,butanefficient,approachistousefine-grainedlocking,sowecanprovideanexclusiveaccessonlytothepartsofthecollectionthatareinuse.Forexample,iftheunderlyingdatastorageisanarray,wecancreatemultiplelocksthatwillcoverthecorrespondingarrayparts.Thisapproachrequiresdeterminingtherequiredlockfirst,butitwillalsoallowanon-blockingaccesstothedifferentpartsofthearray.Thiswilluselocksonlywhenthereisaconcurrentaccesstothesamedata.Incertainscenarios,theperformancedifferencewillbehuge.

NotePLINQusesexactlythesameapproachforparallelcollectionsprocessing.Thereisaspecialmechanismcalledpartitioning,whichsplitsacollectioninmultiplesegments.Eachsegmentgetsprocessedonaseparatethread.AstandardpartitionerimplementationresidesinsidetheSystem.Collections.Concurrent.Partitionertype.

Withthe.NETFramework4.0release,anewsetofconcurrentcollectionsareavailablefor.NETdevelopers.Thesecollectionsarespecificallydesignedforhighloadconcurrentaccessanduselock-freeandfine-grainedapproachesinternally.ThesecollectionsareavailableintheSystem.Collections.Concurrentnamespace:

ConcurrentCollection System.Collections.Genericanalogue

ConcurrentDictionary<TKey,TValue> Dictionary<TKey,TValue>

ConcurrentBag<T> None

ConcurrentQueue<T> Queue<T>

www.EBooksWorld.ir

Page 204: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConcurrentStack<T> Stack<T>

Eachoftheseconcurrentcollectionsaresuitablefordifferentworkscenarios.Further,wewillgothroughallofthesedatastructuresandreviewtheimplementationdetailsandthebest-suitedworkscenario.

www.EBooksWorld.ir

Page 205: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 206: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConcurrentDictionaryWecanimprovetheimplementationofCustomProviderusingConcurrentDictionary<TKey,TValue>tohandlethesynchronization:

publicclassCustomProvider

{

privatereadonly

ConcurrentDictionary<string,OperationResult>_cache=

newConcurrentDictionary<string,OperationResult>();

publicOperationResultRunOperationOrGetFromCache(

stringoperationId)

{

return_cache.GetOrAdd(operationId,

id=>RunLongRunningOperation(id));

}

privateOperationResultRunLongRunningOperation(

stringoperationId)

{

//Runningreallong-runningoperation

//...

Console.WriteLine("Runninglong-runningoperation");

returnOperationResult.Create(operationId);

}

}

Thecodebecamemuchsimpler.WejustusedtheGetOrAddmethodanditdoesexactlywhatweneed;ifthereisanelementinthedictionary,itjustreturnsitsvalueorrunsaprovideddelegate,getstheresultvalue,andstoresitinthedictionary.

Everyconcurrentcollectionimplementsacorrespondinggenericinterface.Forexample,ConcurrentDictionary<TKey,TValue>implementsthestandardIDictionary<TKey,TValue>interface.Howeverbesidesthis,itintroducesnewmethodsbecauseitisnotenoughtointroducethethreadsafeversionofeachmethod.Considerthisexample:

privatereadonlyIDictionary<string,OperationResult>_cache=

newConcurrentDictionary<string,OperationResult>();

publicOperationResultRunOperationOrGetFromCache(

stringoperationId)

{

OperationResultresult;

if(_cache.TryGetValue(operationId,outresult))

{

returnresult;

}

result=RunLongRunningOperation(operationId);

_cache.Add(operationId,result);

returnresult;

}

www.EBooksWorld.ir

Page 207: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thiscodewillnotworkcorrectlyinamultithreadedenvironment.BoththeTryGetValueandAddoperationsarethreadsafe,butasequenceoftwooperationswithoutadditionalsynchronizationcancausearacecondition,andinthisexample,itispossibletogetanexceptionthrownfromtheAddmethodwhiletryingtoaddanelementwhenithasalreadybeenaddedtothedictionarybyanotherthread.

Itisclearthatinthissituation,justhavingtheIDictionary<TKey,TValue>implementationisnotenough.Oneofthepossiblesolutionsistoreplace_cache.Addwiththe_cache.TryAddmethod,butthiswillrequireustogetbacktousingaconcreteclass:

privatereadonly

ConcurrentDictionary<string,OperationResult>_cache=

newConcurrentDictionary<string,OperationResult>();

publicOperationResultRunOperationOrGetFromCache(

stringoperationId)

{

OperationResultresult;

if(_cache.TryGetValue(operationId,outresult))

{

returnresult;

}

result=RunLongRunningOperation(operationId);

_cache.TryAdd(operationId,result);

returnresult;

}

Whilethissolutionisalsofarfromperfect,wecanalreadyseewhyconcurrentcollectionschangedthecommonAPIandintroducedasetofnewmethods.Usually,thesenewmethodsrepresentatomicoperationsthatconsistofseveralstepsandeachstepperformsaspecificactioninternally:GetOrAdd,AddOrUpdate,andsoon.

Nowlet’sreviewonemoreimportantaspectofthisimplementation.Ifwelookatthecodethoroughly,wecanseethatdespitetherebeingnoerrorsintheconcurrentenvironmentitispossiblethattheRunLongRunningOperationmethodcanbecalledtwice.Thus,onlythefirstresultwillbestoredinthedictionaryandthelattermethodcallresultwillbewasted.ThisisalsoimportantbecausetheGetOrAddmethodoftheConcurrentDictionary<TKey,TValue>classisimplementedinaverysimilarway.

ThismeansthatusingRunOperationOrGetFromCacheinaconcurrentenvironmentwillresultincallingalongrunningoperationmultipletimesperonevalue.Ifthisturnsouttobecostly,similartotransmittingalargevolumeofdataviathenetworkorperformingCPUintensivelongtimecalculations,thisisdefinitelynotagoodapproach.

www.EBooksWorld.ir

Page 208: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UsingLazy<T>SinceAddOrGetisimplementedinawaythateverycalltothismethodwiththesamekeywillresultingettingthesamevalue,wecanusealittletricktopreventthelongrunningoperationfromrunningmultipletimes:

privatereadonly

ConcurrentDictionary<string,Lazy<OperationResult>>_cache=

newConcurrentDictionary<string,Lazy<OperationResult>>();

publicOperationResultRunOperationOrGetFromCache(

stringoperationId)

{

return_cache.GetOrAdd(operationId,

id=>newLazy<OperationResult>(

()=>RunLongRunningOperation(id))).Value;

}

Inthisexample,wewraptheRunLongRunningOperationmethodcallintoaspecialobject—Lazy<OperationResult>.Thisclassisapartofthe.NETFrameworkBaseClassLibrary(BCL)thatensuresthattheprovideddelegatewillbeexecutedonlyonceandonlywhenitsValuepropertyisaccessedbyanexternalcode.

WecanlookattheGetOrAddmethodimplementationdetailstofullyunderstandwhatishappeningunderthehood:

//ConcurrentDictionary<TKey,TValue>implementation

publicTValueGetOrAdd(TKeykey,Func<TKey,TValue>valueFactory)

{

TValueresultingValue;

if(TryGetValue(key,outresultingValue))

{

returnresultingValue;

}

TryAddInternal(key,valueFactory(key),false,true,

outresultingValue);

returnresultingValue;

}

///<summary>

///Sharedinternalimplementationforinsertsandupdates.

///Ifkeyexists,wealwaysreturnfalse;

///andifupdateIfExists==truewe

///forceupdatewithvalue;

///Ifkeydoesn'texist,wealwaysaddvalueandreturntrue;

///</summary>

privateboolTryAddInternal(TKeykey,TValuevalue,

boolupdateIfExists,boolacquireLock,

outTValueresultingValue)

{

//...Theimplementationdetails

}

www.EBooksWorld.ir

Page 209: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Note.NETFrameworkCoreisnowopensourceandcanbefoundonGitHubintheMicrosoft/dotnetrepository.However,thereisamoreconvenientwaytolearnthe.NETsourcecode—areferencesource.microsoft.comwebsite.Thisresourcewasspecificallycreatedforlearningtheinternalsof.NETandprovidesacomfortablesearchandnavigationusingthecodesemantics,notjustasimpletextsearch.Forexample,ifyouarelookingforallthecasesoftheSystem.String.Substring(System.Int32)methodusage,youwillnotgetanyotherSubstringmethodoverloads.

Wecanseethatifthereisnocachedoperationresultinthedictionary,weimmediatelycallvalueFactory(key)(thisiswheremultipleRunLongRunningOperationcallshappen),andthereturnedresultgoestotheTryAddInternalmethod.EventhecommentstothismethodstatethatifakeyexistsandtheupdateIfExistsparameterequalstofalse,wewillusetheoldvaluethathasbeenalreadystoredinthedictionary.

UsingLazy<OperationResult>insteadofOperationResultleadstoasituationwherewecallonlytheLazy<T>objectconstructormultipletimes,whilealongrunningoperationwillbeexecutedonlyoncewhenthefirstGetOrAddmethodcallcompletes.

www.EBooksWorld.ir

Page 210: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ImplementationdetailsConcurrentDictionaryisinfactausualhashtablethatcontainsanarrayofbucketsprotectedbyanarrayoflocks.Thenumberoflockscanbedefinedbytheuserandtheoretically,allowsmanythreadstoaccessthedictionarywithoutanycontentioniftheyallusedifferentlocksandthus,thedifferentpartsofdatainthedictionary.

AConcurrentDictionaryinnerstructureschemelookslikethis:

TheentireConcurrentDictionarystateisplacedinaseparateTablesclassinstanceinthem_tablesfield.Thismakesitpossibletohaveanatomicstatechangeoperationforthedictionarywiththehelpofthecompare-and-swap(CAS)operations.

TheTablesclasscontainsthefollowingmostimportantfields:

m_buckets:Thisisanarrayofbuckets;eachofthebucketscontainsasingly-linkedlistofnodeswithdictionarydata.m_locks:Thisisanarrayoflocks;eachlockprovidessynchronizedaccesstooneormorebuckets.m_countPerLock:Thisisanarrayofcounters;eachcountercontainsatotalnumberofnodesthatareprotectedbythecorrespondinglock.Forexample,ifwelookatthepreviousscheme,wherethefirstlockprotectsthefirsttwobuckets,them_countPerLock[0]elementwillcontainthevalueof5.m_comparer:ThisisanIEqualityComparer<TKey>objectthatcontainsthelogicforcalculatingthehashvalueofakeyobject.

TheConcurrentDictionaryclassinturncontainsthreelargeoperationsgroups:

Lock-freeoperations:ThiskindofoperationcanberuninparallelfrommultiplethreadswithoutanycontentionFine-grainedlockoperations:Asithasbeenalreadyexplained,theseoperationscanbeconcurrentlyexecutedwithoutanycontentioniftheymanipulatethedifferentpartsofdatainsidethedictionaryExclusivelockoperations:Theseoperationscanrunonlyonasinglethreadandrequireafullcollectionlocktoensurethreadsafety

Lock-freeoperations

www.EBooksWorld.ir

Page 211: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Theseoperationsdonotrequireanylockandcanbeusedsafelyfrommultiplethreads.Thisisthelistofthecorrespondingmethods:

ContainsKey

TryGetValue

ReadaccessbydictionaryindexerGetEnumerator

ThefirstthreeoperationsarebasedontheTryGetValuemethod.Thiscontainsthefollowingsteps:

1. Getthekeyobjecthashcodeusingcurrentcomparer.2. GetthebucketnumberbythekeyhashwiththehelpoftheGetBucketAndLockNo

method.Thelocknumberisnotusedatthemoment.3. Iterateoverthecurrentbucketnodelisttofindthecorrespondingvalue:

publicboolTryGetValue(TKeykey,outTValuevalue)

{

intbucketNo,lockNoUnused;

Tablestables=m_tables;

GetBucketAndLockNo(

tables.m_comparer.GetHashCode(key),outbucketNo,out

lockNoUnused,

tables.m_buckets.Length,tables.m_locks.Length);

//TheVolatile.Readensuresthattheloadofthe

//fieldsof'n'doesn'tmovebeforetheloadfrombuckets[i].

Noden=Volatile.Read<Node>(reftables.m_buckets[bucketNo]);

//IterateoverNodestofindentrywithacorrespondingkey

...

}

TheGetEnumeratormethodimplementationisquitestraightforward:

publicIEnumerator<KeyValuePair<TKey,TValue>>GetEnumerator()

{

Node[]buckets=m_tables.m_buckets;

for(inti=0;i<buckets.Length;i++)

{

//TheVolatile.Readensuresthat

//theloadofthefieldsof'current'

//doesn'tmovebeforetheloadfrombuckets[i].

Nodecurrent=Volatile.Read<Node>(refbuckets[i]);

while(current!=null)

{

yieldreturnnewKeyValuePair<TKey,TValue>(

current.m_key,current.m_value);

current=current.m_next;

}

www.EBooksWorld.ir

Page 212: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

}

}

Aswecansee,theGetEnumeratormethoddoesnotcreateacopyofbucketscontents,andthisallowsmultiplethreadstochangethedictionarydatawhileanotherthreaditeratesovertheseelements.NoticetheVolatile.Readconstructthatcreatesanacquire-fenceandensuresthatnoreadsorwritescanbereorderedbeforetheloadfrombuckets[i].

Fine-grainedlockoperationsTheseoperationsusuallyworkwithasingleelementinsidethedictionary.Thesemethodsusethefine-grainedlockingapproach:

TryAdd

TryRemove

TryUpdate

writeaccessbyadictionaryindexerGetOrAdd

AddOrUpdate

TheseoperationsinternallyusetheGetBucketAndLockNomethod,whichreturnsthebucketandthelocknumbers.Theimplementationusuallycontainsthefollowingsteps:

1. Getthekeyobjecthashcode.2. Getthebucketandthelocknumbers.3. Acquirethelock.4. Changethecurrentbucket—deleteorchangesomeelementinside.5. Releasetheacquiredlock.

MostoftheoperationsintheprecedinglistusetheTryAddInternalmethodinternally.Let’sreviewthesimplifiedcodeofthismethod:

privateboolTryAddInternal(TKeykey,TValuevalue,

outTValueresultingValue)

{

while(true)

{

boolresizeDesired=false;

vartables=m_tables;

intbucketNo,lockNo;

inthashcode=tables.m_comparer.GetHashCode(key);

GetBucketAndLockNo(hashcode,outbucketNo,outlockNo);

try

{

Monitor.Enter(tables.m_locks[lockNo]);

//Ifthetablejustgotresized,wemaynotbeholding

//therightlock,andmustretry.

//Thisshouldbearareoccurence.

if(tables!=m_tables)

{

www.EBooksWorld.ir

Page 213: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

continue;

}

//LoopingthroughNodesinthebucket.

//IfexistingNodewasfound

//themethodreturnsfalse,otherwise

//newNodewouldbeadded

for(Nodenode=tables.m_buckets[bucketNo];

node!=null;node=node.m_next)

{

//...

}

//Ifthenumberofelementsguardedbythislockhas

//exceededthebudget,resizethebuckettable.

//ItisalsopossiblethatGrowTablewillincrease

//thebudgetbutwon'tresizethebuckettable.

//Thathappensifthebuckettableisfoundtobe

//poorlyutilizedduetoabadhashfunction.

if(tables.m_countPerLock[lockNo]>m_budget)

{

resizeDesired=true;

}

}

finally

{

Monitor.Exit(tables.m_locks[lockNo]);

}

//Resizetableifneeded.

//Thismethodshouldbecalledoutsidethelock

//topreventadeadlocks.

if(resizeDesired)

{

GrowTable(tables,tables.m_comparer);

}

resultingValue=value;

returntrue;

}

}

Itisclearthatthiscodeimplementsalltheprecedingsteps—wegetthekeyhash,thebucket,andthelocknumberandproceedtotheelementneeded.However,thereareacoupleofimportantpointstopayattentionto:

Usingthewhilelooptoworkaroundthesituationwhereanotherthreadhaschangedthecollectionanditsm_tablesfield.Inthiscase,wejustretryuntilwesucceedandtheoldandnewm_tablesvaluesremainequal.Whennodecountperonelockexceedssomethresholdvalue(m_budget),thehashtablerebalancingoccursinsidetheGrowTablemethod.Thisrequiresanexclusivelockforthedictionarytobeacquired.

Exclusivelockoperations

www.EBooksWorld.ir

Page 214: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TherearemoreoperationsthatarerequiredtogetanexclusivelockastheGrowTabledoes.Itisveryimportanttoknowtheseoperationsandavoidusingtheminamultithreadedenvironmentifpossible.Hereistheoperationslist:

Clear

ToArray

CopyTo

Count

IsEmpty

GetKeys

GetValues

Werememberthattryingtoworkwithmultiplelockscaneasilyleadtodeadlocksinaconcurrentprogram.Fortunately,theconcurrentdictionarycontainstheAcquireLocksmethodthatcansafelyacquiremultiplelocksalwaysinthesameorderthatpreventsdeadlocks.ThismethodisusedinternallyfromtheAcquireAllLocksmethod,whichsafelyacquiresallthelocksinthedictionary.

Everyoperationlistedpreviouslyusesthesamealgorithm;first,itcallsAcquireAllLockstopreventconcurrentchangestothedictionary,thenitmodifiesthem_tableinstanceandchangesthedictionarystate.Forexample,hereishowtheCountpropertyisimplemented:

publicintCount

{

get

{

intcount=0;

try

{

//Acquirealllocks

AcquireAllLocks();

//Computethecount,weallowoverflow

for(inti=0;i<m_tables.m_countPerLock.Length;i++)

{

count+=m_tables.m_countPerLock[i];

}

}

finally

{

//Releaselocksthathavebeenacquiredearlier

ReleaseLocks();

}

returncount;

}

}

www.EBooksWorld.ir

Page 215: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UsingtheimplementationdetailsinpracticeKnowingtheprinciplesofhowtheconcurrentdictionaryisimplementedcanhelpyouinsomepracticalsituations.

Abetterunderstandingofconcurrentdictionaryconstructorparameters,forexample,concurrencyLevel,willhelptotuneupyourdatastructurefortheconcretetask.Ononehand,themorelockswecreate,themorethreadscanpotentiallyworkwiththedictionarywithoutlocking,whichisagoodthing.Ontheotherhand,creatingmorelockscreatesmoreperformanceoverhead,andwecannotexplicitlysetalockcontrolorabucket,sothiscanleadtodeclineofperformance.Knowingthesedetailswillhelpustostudytheprogramunderaprofilertofindthebestsolutionforourconcretecase.

Anotherimportantimplementationaspectisthedictionarybucketscontainingsingly-linkedlists.AddinganelementtosuchalistisanO(N)operationandthiscanbeaproblemwhenstoringhundredsofthousandsofsmallitemsinthedictionary.

SincetheCount,ToArray,andIsEmptyoperationsrequireexclusivelocking,insomecasesusingcorrespondingLINQalternativessuchasEnumerable.Count(),Enumerable.ToArray(),andEnumerable.Any()willbemuchmoreefficientinsituationswherethedictionaryoftengetsconcurrentlyupdated.

www.EBooksWorld.ir

Page 216: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 217: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConcurrentBag<T>ConcurrentBag<T>isoneofthesimplestconcurrentcollections.Itisintendedtostoreanygeneral-purposedata.Themainfeatureofthiscollectionishowitstoresthedata;theAddmethodappendsanitemtoadoubly-linkedlistthatisstoredinthecurrentthread’slocalstorage.Thismakestheappendingoperationveryefficient,sincethereisnocontention.GettinganitemfromthecollectionwiththeTryTakeorTryPeekmethodsisalsoquiteefficient.First,welookfortheiteminthelocallist,butifitisempty,welookforitemsinotherthreads’locallists.

Thisapproachiscalledworkstealingandworkswellwheneachthreadcontainsmoreorlessthesamenumberofdataandusesthesamenumberofappendandtakeoperations.

Let’sreviewanexampleofusingtheConcurrentBag<T>datastructure:

varbag=newConcurrentBag<string>();

vartask1=Run(()=>

{

AddAndPrint(bag,"[T1]:Item1");

AddAndPrint(bag,"[T1]:Item2");

AddAndPrint(bag,"[T1]:Item3");

Thread.Sleep(2000);

TakeAndPrint(bag);

TakeAndPrint(bag);

},threadName:"T1");

vartask2=Run(()=>

{

AddAndPrint(bag,"[T2]:Item1");

AddAndPrint(bag,"[T2]:Item2");

AddAndPrint(bag,"[T2]:Item3");

Thread.Sleep(1000);

TakeAndPrint(bag);

TakeAndPrint(bag);

TakeAndPrint(bag);

TakeAndPrint(bag);

},threadName:"T2");

Task.WaitAll(task1,task2);

TheAddAndPrint,TakeAndPrintandRunmethodshelptocreateathreadwithagivennameandallowsustoappendandremoveelementsfromtheConcurrentBag<T>object,whileprintingtheelementvaluetotheconsole:

privatestaticTaskRun(Actionaction,stringthreadName)

{

vartcs=newTaskCompletionSource<object>();

varthread=newThread(()=>

{

action();

tcs.SetResult(null);

www.EBooksWorld.ir

Page 218: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

});

thread.Name=threadName;

thread.Start();

returntcs.Task;

}

privatestaticvoidAddAndPrint(ConcurrentBag<string>bag,

stringvalue)

{

Console.WriteLine("{0}:Add-{1}",

Thread.CurrentThread.Name,value);

bag.Add(value);

}

privatestaticvoidTakeAndPrint(ConcurrentBag<string>bag)

{

stringvalue;

if(bag.TryTake(outvalue))

{

Console.WriteLine("{0}:Take-{1}",

Thread.CurrentThread.Name,value);

}

}

Herewecreatedtwotasks,andeachtasksetstwoelementstothequeue.Thenitwaitsforsometimeandstartstoprocesstheappendedelements.TheinnerstoragestructureoftheConcurrentBagobjectwilllooklikethiswhentheappendingoftheelementsisfinished:

ConcurentBag<T>,aswehavealreadymentioned,containsseveraldoubly-linkedlists,

www.EBooksWorld.ir

Page 219: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

onelistforeachthread.Addinganitemleadstoappendingittotheendofthelocallist,butgettingitemsfromtheconcurrentbagisslightlymorecomplicated:

T2:Add-[T2]:Item1

T1:Add-[T1]:Item1

T2:Add-[T2]:Item2

T2:Add-[T2]:Item3

T1:Add-[T1]:Item2

T1:Add-[T1]:Item3

T2:Take-[T2]:Item3

T2:Take-[T2]:Item2

T2:Take-[T2]:Item1

T2:Take-[T1]:Item1

T1:Take-[T1]:Item3

T1:Take-[T1]:Item2

Weappenditemstothecollectionfromtwothreads,andthisexplainsanadditionorderthatwasdemonstratedpreviously.ThemostinterestingthingishowitemsareremovedfromConcurrentBag.Inourcase,thesecondthreadstartsgettingtheitemsfromthecollection.First,itgetstheelementsthatwereaddedbythisthread,butinthereverseorder(fromtheendofthedoubly-linkedlist).Whenthelocallistbecomesempty,ittriesto“steal”workfromanotherthread,butthistimeitgetsitemsfromthebeginningoftheunderlyinglist.

www.EBooksWorld.ir

Page 220: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConcurrentBaginpracticeTheimplementationdetailsoftheConcurentBag<T>datastructuremakesitusefulonlyinveryspecificscenarios.Readingandwritingobjectshastohappenonthesamethreadtominimizecontention.Itmakesthiscollectionnotveryusefulinmostcommonsituations,sinceusuallydifferentthreadsappendandreaddatafromacollection.

AgoodpracticalscenarioforConcurentBag<T>isanobjectpool.Itisusuallyimplementedinawaythatwhensomeobject,whichissignificantlyexpensivetocreate,doesnotgetcleanedupbythegarbagecollector,itgoestosomeobjectstorageandiseasilyaccessedwhenneeded.Sinceusuallysuchoperationshappenonasinglethread,thiswillmakeaperfectconditiontousethiskindofconcurrentcollection.

Anothersimilarexampleisathreadpoolimplementation.IfwelookcloselyattheDefaultTaskSchedulerimplementationfromTaskParallelLibrary,wecanseethatithasthesamebehaviorastheconcurrentbag.Thistaskschedulerdoesnotuseaglobaltasklist;instead,itcreatesanumberoflocaltasklistsforeachworkerthread.Ifsometaskcreatesachildtask(withoutprovidingthePreferFairnessoption),itwillbeappendedtothelocaltasklist.ThishelpstoreducecontentionandhasahigherprobabilityoffindingtherequireddataintheCPUcache.Alsoitusesworkstealingincasethelocaltasklistisempty.

However,eveniftheconcurrentbagperfectlyfitsinyourscenario,itisagoodideatotrytouseotherdatastructuresandmeasureandcomparetheperformanceofeachimplementation.Thesynthetictests(theycanbefoundinthecodesamplesofthischapter)showthattheConcurrentBag<T>performanceisnotimpressive,andmaybechoosingConcurrentQueue<T>orConcurrentStack<T>willbeabettersolution.Eveninperfectconditionswhenthesamethreadappendsandretrievesdata,aconcurrentbagisaboutthreetimesslowerthanaconcurrentqueue.

www.EBooksWorld.ir

Page 221: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 222: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConcurrentQueue<T>ConcurrentQueue<T>isaconcurrentversionoftheQueue<T>class.Itcontainsthreebasicmethods:Enqueueappendsanitemtothequeue,TryDequeueretrievesanitemfromthequeueifitispossible,andTryPeekgetsthefirstelementinthequeuewithoutremovingitfromthequeue.Thelasttwomethodsreturnfalseifthequeueisempty.

Nowlet’sseeasamplecodeforConcurrentQueue<T>:

varqueue=newConcurrentQueue<string>();

vartask1=Run(()=>

{

AddAndPrint(queue,"[T1]:Item1");

AddAndPrint(queue,"[T1]:Item2");

AddAndPrint(queue,"[T1]:Item3");

Thread.Sleep(2000);

TakeAndPrint(queue);

TakeAndPrint(queue);

},threadName:"T1");

vartask2=Run(()=>

{

AddAndPrint(queue,"[T2]:Item1");

AddAndPrint(queue,"[T2]:Item2");

AddAndPrint(queue,"[T2]:Item3");

Thread.Sleep(1000);

TakeAndPrint(queue);

TakeAndPrint(queue);

TakeAndPrint(queue);

TakeAndPrint(queue);

},threadName:"T2");

Task.WaitAll(task1,task2);

Inthisexample,wedothesamewiththeConcurrentBag<T>code.Wecreatetwonamedthreads;eachthreadappendsthreeitemstothequeue.Thenaftersomepause,threadsstarttoretrievetheelementsfromthequeue:

T1:Add-[T1]:Item1

T2:Add-[T2]:Item1

T2:Add-[T2]:Item2

T2:Add-[T2]:Item3

T1:Add-[T1]:Item2

T1:Add-[T1]:Item3

T2:Dequeue-[T1]:Item1

T2:Dequeue-[T2]:Item1

T2:Dequeue-[T2]:Item2

T2:Dequeue-[T2]:Item3

T1:Dequeue-[T1]:Item2

T1:Dequeue-[T1]:Item3

ConcurrentqueueisaFIFO(FirstIn,FirstOut)collection,butsincethisisa

www.EBooksWorld.ir

Page 223: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

multithreadedenvironment,theorderofappendingandremovingelementsisnotstrictlysequential.

TheConcurrentQueue<T>classisimplementedonasingly-linkedlistofringbuffers(orsegments).Thisallowsthiscollectiontobelock-freethatmakesitveryattractivetousethisinhighloadconcurrentapplications.

Inthebeginning,aconcurrentqueuecreatesonesegmentthatisreferencedbytwoinnerfields:m_headandm_tail(thefirstandthelastsegmentsreferencecorrespondingly).Thesegmentsizeis32bytes,andeachsegmentcontainstworeferences:LowandHigh.LowreferencesanelementpositioninthebufferthatcanberemovedbycallingtheDequeuemethod,andHighreferencesthelastiteminthebufferthathasbeenaddedbyusingtheEnqueuemethod.

Hereishowthequeuewilllookinternallyafterappendingsixelementsandthenremovingtwoofthem:

Ifwefindoutduringtheprocessofappendinganelementtothequeuethatthesegmentisfull,thenonemoresegmentiscreatedandattachedtotheendofthesegmentlist.Onlythefirstandthelastsegmentscanbepartiallyfull,everyothersegmentmustbecompletelyfull.

Ifweappend80elementsandthenremovefour,wewillseesomethinglikethis:

Theoverallqueuesizewillbe32–4+32+16=76.Thequeuewillcontainthreesegments,andthefirstandthelastsegmentswillbepartiallyfilled.

www.EBooksWorld.ir

Page 224: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 225: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConcurrentStack<T>TheConcurrentStack<T>datastructureisaconcurrentversionofastandardStack<T>collection.Itcontainsthreemainmethods:Push,TryPop,andTryPeek,toappend,retrieveandgettheitemfromthecollectionbyFILO(FirstIn,LastOut)principle.

ConcurrentStack<T>isimplementedasasingly-linkedlock-freelist,whichmakesitlessinterestingintermsofreviewingtheimplementationdetails.Nevertheless,itisstillusefultoknow,andifwehavetochooseaconcurrentdatastructureforascenariowhereelementsprocessingorderisnotimportant,itispreferabletouseaconcurrentqueuesinceithaslessperformanceoverhead.Appendingelementstotheconcurrentstackalwaysleadstoadditionalmemoryallocation,whichcanbeasignificantdrawbackincertainscenarios.

www.EBooksWorld.ir

Page 226: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 227: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TheProducer/ConsumerpatternTheProducer/Consumerpatternisoneofthemostwidelyusedparallelprogrammingpatterns.Themostnaturalapproachistoorganizeyourapplicationforprocessingworkitemsonanotherthread.Inthiscase,wegettwoapplicationparts—oneputsnewworktobeprocessedandtheotherchecksfornewworkandperformselementprocessing.Thestandard.NETFrameworkthreadpoolisagoodexample;onethreadputsaworkiteminaprocessingqueuebycallingtheTask.RunfunctionoftheThreadPool.QueueUserWorkItemmethods,andtheinfrastructurefindsotherthreadstoprocessthesetasks.

NoteTheotherparallelprogrammingpatternswillbereviewedinthenextchapter.TheProducer/Consumerpatternisverytightlyrelatedtoconcurrentdatastructures,anditismorenaturallydescribedalongwiththem.

Anotherclassicexampleisauserinterfaceprogramming.TocreateresponsiveandfastUI,aUIthreadhastooffloadasmuchworkaspossibletootherthreads.Therefore,itpoststaskstoaqueue,andsomebackgroundthreadsprocessthesetasksandprovidetheresultbacktoUI.

Thesameapproachisusedinserver-sideprogramming.Toeffectivelyprocessclientrequests,theyarequeuedfirst,andonlythendoestheserverinfrastructureassignaworkerthreadtoprocesstheuserrequest.

www.EBooksWorld.ir

Page 228: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CustomProducer/ConsumerpatternimplementationLet’strytoimplementtheProducer/ConsumerpatternwiththehelpofthestandardQueue<T>class.Beforewecangettotheprogramming,wehavetothinkabouttherequirements:

WhatshouldaconsumerdowhencallingtheTakemethodwhilethecurrentqueuedoesnotcontainanyelements?WhatshouldaproducerdowhencallingtheAddmethodasthecollectionsizehasreachedsomethresholdvalue?

Ifwelookatthestandardconcurrentcollectionsimplementation,itmakessensetoreplacetheTakemethodwithTryTake,andthiswillreturnfalseifthequeueisempty.InsteadoftheAddmethod,wecanimplementTryAddthatwillreturnfalsewhenthequeueisfull.Unfortunately,itisnotthebestdesignforaProducer/Consumerqueue.

AmorenaturalapproachwouldbetomaketheTakemethodblockthecurrentthreadwhentheunderlyingqueueisemptyandreturntheresultassoonasanyproducerthreadaddsanitemtothequeue;suchaqueueiscalledablockingqueue.ThesamewiththeAddmethod—justblockwhenthequeueisfullandputanitemassoonasthereisaplaceforaniteminthequeue.Thisapproachhelpsustohandleasituationwhentherearetoomanyproducersortheyjustcreatemoreitemsthatconsumerscanhandle.Thiskindofqueueiscalledaboundedqueue.

AsimpleBoundedBlockingQueueimplementationwilllooklikethis:

publicclassBoundedBlockingQueue<T>

{

privatereadonlyQueue<T>_queue=newQueue<T>();

privatereadonlySemaphoreSlim_nonEmptyQueueSemaphore=

newSemaphoreSlim(0,int.MaxValue);

privatereadonlySemaphoreSlim_nonFullQueueSemaphore;

publicBoundedBlockingQueue(intboundedCapacity)

{

_nonFullQueueSemaphore=newSemaphoreSlim(

boundedCapacity);

}

publicvoidAdd(Tvalue)

{

_nonFullQueueSemaphore.Wait();

lock(_queue)_queue.Enqueue(value);

_nonEmptyQueueSemaphore.Release();

}

publicTTake()

{

_nonEmptyQueueSemaphore.Wait();

Tresult;

www.EBooksWorld.ir

Page 229: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

lock(_queue)

{

Debug.Assert(_queue.Count!=0);

result=_queue.Dequeue();

}

_nonFullQueueSemaphore.Release();

returnresult;

}

}

Thisimplementationusesasimplequeueandtwosemaphores—_nonFullQueueSemaphoreand_nonEmptyQueueSemaphore.Weusethefirstonetoblockproducerswhenthequeueisfull;thesecondblocksconsumerswhenthequeueisempty.WhentheAddmethodiscalled;wecallWaiton_nonFullQueueSemaphore.Itwillreturncontrolwhenthequeueisnotfull,andthenwecanaddanothersemaphorecountertounblockconsumerthreads.TheTakemethodworksexactlylikethis,butinareverseorder—wewaitonthe_nonEmptyQueueSemaphoresemaphoreuntilwehaveanythinginthequeue,andthenweremovetheappearedelementfromthequeueandincreasetheothersemaphorecounter.

TipIntheproductioncode,wewillhavetoimplementIDisposabletosupportdeterministicresourcesreleasing,properexceptionhandling,andcancellationpolicybyprovidingtheCancellationTokeninstancetotheAddandTakemethods.However,inthisexample,itisnotrelevanttothetopicandthislogicisomittedtokeeptheremainingcodecleanandsimple.

Insomecases,theProducer/Consumerqueuecanbeusedtoprocessafixed(oratleastafinite)numberofelements.Inthiscase,weneedtobeabletonotifytheconsumersthatitemsappendingisover:

publicclassBoundedBlockingQueue<T>

{

privatereadonlyQueue<T>_queue=newQueue<T>();

privatereadonlySemaphoreSlim_nonEmptyQueueSemaphore=

newSemaphoreSlim(0,int.MaxValue);

privatereadonly

CancellationTokenSource_consumersCancellationTokenSource=

newCancellationTokenSource();

privatereadonlySemaphoreSlim_nonFullQueueSemaphore;

publicBoundedBlockingQueue(intboundedCapacity)

{

_nonFullQueueSemaphore=newSemaphoreSlim(boundedCapacity);

}

publicvoidCompleteAdding()

{

//Notifyalltheconsumersthatcompletionisfinished

www.EBooksWorld.ir

Page 230: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

_consumersCancellationTokenSource.Cancel();

}

publicvoidAdd(Tvalue)

{

_nonFullQueueSemaphore.Wait();

lock(_queue)_queue.Enqueue(value);

_nonEmptyQueueSemaphore.Release();

}

publicTTake()

{

Titem;

if(!TryTake(outitem))

{

thrownewInvalidOperationException();

}

returnitem;

}

publicIEnumerable<T>Consume()

{

Telement;

while(TryTake(outelement))

{

yieldreturnelement;

}

}

privateboolTryTake(outTresult)

{

result=default(T);

if(!_nonEmptyQueueSemaphore.Wait(0))

{

try

{

_nonEmptyQueueSemaphore.Wait(

_consumersCancellationTokenSource.Token);

}

catch(OperationCanceledExceptione)

{

//Breakingthelooponlywhencancellation

//wasrequestedbyCompleteAdding

if(e.CancellationToken==

_consumersCancellationTokenSource.Token)

{

returnfalse;

}

//Propagateoriginalexception

throw;

}

www.EBooksWorld.ir

Page 231: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

}

lock(_queue)

{

result=_queue.Dequeue();

}

_nonFullQueueSemaphore.Release();

returntrue;

}

}

HereweseenewCompleteAddingandConsumemethods.Thefirstoneisintendedtobeusedfromproducer’scodetosignalthatwehavefinishedappendingitemstothequeue.TheConsumemethodcanbeusedbyconsumerstoprocessalltheitemsuntilthequeueisemptyanditemappendingiscomplete.

WehavealsoimplementedacooperativecancellationherewiththehelpoftheCancellationTokenSourceandCancellationTokenobjects.TheCompleteAddingmethodsetstheflagthatindicatesthatnoadditionalelementswillbeaddedtothecollection.TheTryTakemethodusesthisflagandstandardsemaphorecancellationlogictobreaktheloopwhencancellationisrequested.

Wecanuseourbrandnewcollectioninthefollowingway:

varqueue=newBoundedBlockingQueue<string>(3);

vart1=Task.Run(()=>

{

AddAndPrint(queue,"1");

AddAndPrint(queue,"2");

AddAndPrint(queue,"3");

AddAndPrint(queue,"4");

AddAndPrint(queue,"5");

queue.CompleteAdding();

Console.WriteLine("[{0}]:finishedproducingelements",

Thread.CurrentThread.ManagedThreadId);

});

vart2=Task.Run(()=>

{

foreach(varelementinqueue.Consume())

{

Print(element);

}

Console.WriteLine("[{0}]:Processingfinished.",

Thread.CurrentThread.ManagedThreadId);

});

vart3=Task.Run(()=>

{

foreach(varelementinqueue.Consume())

www.EBooksWorld.ir

Page 232: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

Print(element);

}

Console.WriteLine("[{0}]:Processingfinished.",

Thread.CurrentThread.ManagedThreadId);

});

Task.WaitAll(t1,t2,t3);

Inthiscode,weusedoneproducerthreadthatappendsitemstothequeue,andtwoconsumerthreads.Theresultwillbethefollowing:

[4]:Added1

[9]:Took1

[8]:Took2

[4]:Added2

[4]:Added3

[4]:Added4

[4]:Added5

[9]:Took3

[9]:Took5

[4]:finishedproducingelements

[8]:Took4

[9]:Processingfinished.

[8]:Processingfinished.

www.EBooksWorld.ir

Page 233: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 234: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TheProducer/Consumerpatternin.NET4.0+Since.NETFramework4.0,therehasbeenastandardBlockingCollection<T>class,soweshouldpreferusingthistocreateourownimplementationssuchasBoundedBlockingQueue<T>.Itcontainsalltherequiredoperationsandallowsustochoosedifferentelementstoragestrategiesusingdifferentconcurrentcollections.

InspiteofBlockingCollection<T>implementingtheICollection<T>interface,itisjustawrapperoveranygeneralconcurrentcollectionthatimplementsIProducerConsumerCollection<T>.TheBlockingpartofthecollectionnamemeansthattheTakemethodblocksuntilnewelementsappearinthecollection.AmoreaccuratenameforthiscollectionwouldbeBoundedBlockingProducerConsumer<T>,sinceitalsoblockstheAddmethodwhenthemaximumunderlyingcollectioncapacityisreached.

Let’suseBlockingCollection<T>tocreateacustomProducer/Consumerimplementationthatallowsustocreateaspecificnumberofconsumerthreads:

publicclassCustomProducerConsumer<T>:IDisposable

{

privatereadonlyAction<T>_consumeItem;

privatereadonlyBlockingCollection<T>_blockingCollection;

privatereadonlyTask[]_workers;

publicCustomProducerConsumer(Action<T>consumeItem,

intdegreeOfParallelism,

intcapacity=1024)

{

_consumeItem=consumeItem;

_blockingCollection=newBlockingCollection<T>(capacity);

_workers=Enumerable.Range(1,degreeOfParallelism)

.Select(_=>Task.Factory.StartNew(Worker,

TaskCreationOptions.LongRunning))

.ToArray();

}

publicvoidProcess(Titem)

{

_blockingCollection.Add(item);

}

publicvoidCompleteProcessing()

{

_blockingCollection.CompleteAdding();

}

publicvoidDispose()

{

//Unblockallworkerseveniftheclient

//didn'tcallCompleteProcessing

www.EBooksWorld.ir

Page 235: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

if(!_blockingCollection.IsAddingCompleted)

{

_blockingCollection.CompleteAdding();

}

Task.WaitAll(_workers);

_blockingCollection.Dispose();

}

privatevoidWorker()

{

foreach(varitemin

_blockingCollection.GetConsumingEnumerable())

{

_consumeItem(item);

}

}

TheconstructorofCustomProducerConsumer<T>acceptsasaparameteranAction<T>delegatethatrepresentstheconsumer,queuesize,andrequiredparallelismdegree.Then,wecreatetherequirednumberofworkerthreadsbycreatingtheTaskobjectswiththeTaskCreationOptions.LongRunningoption.Theprocessmethodisintendedtoappendnewelements,andtheCompleteProcessingmethodsignalsthattherewillbenomoreelementsappendedtothequeue:

Action<string>processor=element=>

{

Console.WriteLine("[{0}]:Processingelement'{1}'",

Thread.CurrentThread.ManagedThreadId,element);

};

varproducerConcumer=newCustomProducerConsumer<string>(

processor,Environment.ProcessorCount);

for(inti=0;i<5;i++)

{

stringitem="Item"+(i+1);

Console.WriteLine("[{0}]:Addingelement'{1}'",

Thread.CurrentThread.ManagedThreadId,item);

producerConcumer.Process("Item"+(i+1));

}

Console.WriteLine("[{0}]:Completeaddingnewelements",

Thread.CurrentThread.ManagedThreadId);

producerConcumer.CompleteProcessing();

//Disposewillblocktillalloperationsgetscompleted

producerConcumer.Dispose();

Ifwerunthiscode,wewillgetthefollowingresult:

[5]:Addingelement'Item1'

[5]:Addingelement'Item2'

www.EBooksWorld.ir

Page 236: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

[5]:Addingelement'Item3'

[9]:Processingelement'Item1'

[8]:Processingelement'Item2'

[5]:Addingelement'Item4'

[5]:Addingelement'Item5'

[5]:Completeaddingnewelements

[9]:Processingelement'Item5'

[10]:Processingelement'Item3'

[11]:Processingelement'Item4'

Theresultshowsthatthereisoneproducerthreadthatappendselementstothecollection,andfourdifferentconsumerthreadsthatprocesstheseelementsuntiltheproducerthreadstopsappendingitems.

www.EBooksWorld.ir

Page 237: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 238: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,wehavelearnedaboutdifferentconcurrentdatastructures,theiradvantagesanddisadvantages,andwehaveunderstoodthatchoosinganappropriateconcurrentdatastructureisacomplicatedandresponsibletask.Therightchoiceisdefinedbymanycriteriasuchasavailability,complexity,resourceconsumption,versatility,performance,andmanyothers.

Similartosoftwaredevelopment,ingeneralthereisnosingleandproperuniversalsolutionappropriateforallusagescenarios.Insomecases,itisbettertouseregularcollectionswithexclusivelocking.Someothercaseswillrequiredevelopingourownspecificconcurrentdatastructuresfromscratch,sinceauniversalstandardcollectionwillnotfitinthehighperformancerequirements.Aruleofthumbistotrytoimplementtheeasiestsolutionandthenmeasuretheperformanceandcheckwheretheperformancebottleneckofyourapplicationis.

Inthenextchapter,wewillconsiderdifferentconcurrentandasynchronousprogrammingpatternsthatcanhelpinstructuringyourparallelprogramforsimplicityandefficiencyandallowyoutoquicklyimplementwell-knownconcurrentalgorithms.

www.EBooksWorld.ir

Page 239: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 240: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter7.LeveragingParallelPatternsTherearemanyprogrammingrules,tricks,andtypicalpatternsrelatedtoconcurrentprogrammingthathavebeendevelopedtoaddressconcreteproblemsthatoftenhappeninpractice.Inthischapter,wewillgothroughseveralkindsofconcurrentprogrammingpatterns—low-levelpatterns(concurrentidioms),.NET-specificpatternsforasynchronousprogramming(AsynchronousProgrammingPatterns),andhigh-levelconcurrentapplicationbuildingblocks(ConcurrentDesignPatterns).Let’sreviewthemonebyone.

www.EBooksWorld.ir

Page 241: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConcurrentidiomsThe.NETFrameworkplatformcontainssomehigh-levelcomponentsthatmakeconcurrentapplicationsprogrammingmucheasier.InChapter6,UsingConcurrentDataStructures,wereviewedconcurrentcollectionsanddatastructures,andinChapter4,TaskParallelLibraryinDepth,andChapter5,C#LanguageSupportforAsynchrony,welookedatTaskParallelLibraryandtheC#languageasync/awaitinfrastructure.

Here,wewillseehowTPLandC#canimproveyourprogrammingexperience.

www.EBooksWorld.ir

Page 242: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ProcessTasksinCompletionOrderAsanexampletask,let’sconsiderleveragingaweatherinformationfromaserviceforeachprovidedcity,processingtheinformation,andprintingittotheconsole.Thesimpleimplementationwillbelikethis:

publicasyncTaskUpdateWeather()

{

varcities=newList<string>{"LosAngeles","Seattle","NewYork"};

vartasks=

fromcityincities

selectnew{City=city,WeatherTask=GetWeatherForAsync(city)};

foreach(varentryintasks)

{

varweather=awaitentry.WeatherTask;

ProcessWeather(entry.City,weather);

}

}

privateTask<Weather>GetWeatherForAsync(stringcity)

{

Console.WriteLine("Gettingtheweatherfor'{0}'",city);

returnWeatherService.GetWeatherAsync(city);

}

privatevoidProcessWeather(stringcity,Weatherweather)

{

Console.WriteLine("[{2}]:Processingweatherfor'{0}':'{1}'",

city,weather,DateTime.Now.ToLongTimeString());

}

Inthiscode,weusedaLINQquerytogettheweatherdataforeachcity.Theprogramwillworkwell,butthereisaprobleminthiscode;wecalltheweatherinfoserviceonebyoneandanewrequestgetsissuedonlyaftertheprecedingrequesthasbeencompleted.WecanuseaworkaroundbycallingtheToListmethodonthequery,butwewillgettheresultsintheirstartingorderandnotbytaskcompletion.

ThesolutionistousetheProcessTasksinCompletionOrderidiom.TheimplementationisbasedontheTask.WhenAnymethod:

varcities=newList<string>{"LosAngeles","Seattle","NewYork"};

vartasks=cities.Select(asynccity=>

{

returnnew{City=city,Weather=awaitGetWeatherForAsync(city)};

}).ToList();

while(tasks.Count!=0)

{

varcompletedTask=awaitTask.WhenAny(tasks);

tasks.Remove(completedTask);

www.EBooksWorld.ir

Page 243: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

varresult=completedTask.Result;

ProcessWeather(result.City,result.Weather);

}

Here,wecalledtheweatherinformationserviceforallthecitiesinparallel,andtheninsidethewhileloopweusedtheTask.WhenAnymethodtogetthefirstcompletedtask.Thetaskgetsprocessedandremovedfromtherunningtasklist.Asrequiredinthisexample,tasksarebeingprocessedincompletionorder.

However,thecodelooksmorecomplicatedthanthefirstsample.Togetthecodestructured,wecancreateagenericOrderByCompletionimplementationforthetaskscollection:

publicstaticIEnumerable<Task<T>>OrderByCompletion<T>(

thisIEnumerable<Task<T>>taskSequence)

{

vartasks=taskSequence.ToList();

while(tasks.Count!=0)

{

vartcs=newTaskCompletionSource<T>();

//Gettingthefirstfinishedtask

Task.WhenAny(tasks).ContinueWith((Task<Task<T>>tsk)=>{

tasks.Remove(tsk.Result);

tcs.FromTask(tsk.Result);

});

yieldreturntcs.Task;

}

}

NoteNevertheless,thisimplementationhasaseriouspitfall.SincetheTask.WhenAnymethodcreatesacontinuationtaskforeachrunningtaskandwearecallingitinsidetheloop,wecanconcludethatthisOrderByCompletionmethodimplementationhasatimecomplexityofO(n2).Toimprovetheperformance,wecanregisteracontinuationforeachtaskthatwillusetheTaskCompletionSourcearraytostoreeachtask’sresult.

ItisverycomfortabletousethenewlyimplementedOrderByCompletionmethod:

varcities=newList<string>{"LosAngeles","Seattle","NewYork"};

vartasks=cities.Select(asynccity=>

{

returnnew{City=city,Weather=awaitGetWeatherForAsync(city)};

});

foreach(vartaskintasks.OrderByCompletion())

{

vartaskResult=awaittask;

www.EBooksWorld.ir

Page 244: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

//taskResultisanobjectofanonymoustypewithCityand

//WeatherTask

ProcessWeather(taskResult.City,taskResult.Weather);

}

Nowitispossibletousetheplainoldforeachloopsimilarlytothefirstimplementation,butthetaskprocessinghappensbycompletionandnotbystartorder.Theresultswilldemonstratethisprocessingbehavior:

[12:54:35PM]:Gettingtheweatherfor'LosAngeles'

[12:54:35PM]:Gettingtheweatherfor'Seattle'

[12:54:35PM]:Gettingtheweatherfor'NewYork'

[12:54:36PM]:Processingweatherfor'Seattle':'Temp:7C'

Gottheweatherfor'LosAngeles'

[12:54:39PM]:Processingweatherfor'LosAngeles':'Temp:6C'

Gottheweatherfor'NewYork'

[12:54:40PM]:Processingweatherfor'NewYork':'Temp:8C'

www.EBooksWorld.ir

Page 245: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

LimitingtheparallelismdegreeTousecomputerresourceseffectively,weneedtobeabletospecifythenumberofsimultaneouslyrunningoperations.Besidesthis,theoptimalparalleloperationsnumberisrelatedtotheirnature.Iftheseoperationsarelong-runningandCPU-bound,itmakessensetousethenumberofhardware-supportedthreadstolimittheparallelismdegree.

However,iftheyareIO-bound,thereisnoclearlimit.ItdependsonmanyfactorsrelatedtothekindofIOthatishappeningandthecorrespondinghardwarecharacteristics.ItmaybeHDDrandomreadspeed,ornetworkthroughputandlatency,orinthecaseofremoteservicecalls,theperformanceofthisservice,andsoon.Creatingageneralsolutioninthiscaseisveryhardandcanbemorecomplicatedthancreatingourownimplementationofathreadpool,whichdoesthesameforCPU-boundtasks.

However,forstarters,wecanjustrunmultipleparalleloperationsandlimittheparallelismdegreewithacertainnumber.Let’spretendthatwedidexperimentswithourweatherinfoserviceandfoundoutbymeasurementsthatthemosteffectiveoptionistorunonlytwosimultaneousrequeststothisservice.

OneofthewaysofimplementingsuchalimitisbycreatingaForEachAsyncextensionmethodthatacceptadegreeOfParallelismparameter:

publicstaticIEnumerable<Task<TTask>>

ForEachAsync<TItem,TTask>(

thisIEnumerable<TItem>source,

Func<TItem,Task<TTask>>selector,

intdegreeOfParallelism)

{

//Weneedtoknowalltheitemsinthesource

//beforestartingtasks

vartasks=source.ToList();

intcompletedTask=-1;

//CreatinganarrayofTaskCompletionSourcethatwouldhold

//theresultsforeachoperations

vartaskCompletions=newTaskCompletionSource<TTask>[tasks.Count];

for(intn=0;n<taskCompletions.Length;n++)

taskCompletions[n]=newTaskCompletionSource<TTask>();

//Partitionerwoulddoallgruntworkforusandsplit

//thesourceintoappropriatenumberofchunks

//forparallelprocessing

foreach(varpartitioninPartitioner.Create(tasks).

GetPartitions(degreeOfParallelism)){

varp=partition;

//Loosingsynccontextandstartingasynchronous

//computationforeachpartition

Task.Run(async()=>

{

while(p.MoveNext())

www.EBooksWorld.ir

Page 246: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

vartask=selector(p.Current);

//Don'twanttouseemptycatch.

//Thistrickjustswallowsanexception

awaittask.ContinueWith(_=>{});

intfinishedTaskIndex=Interlocked.Increment(

refcompletedTask);

taskCompletions[finishedTaskIndex]

.FromTask(task);

}

});

}

returntaskCompletions.Select(tcs=>tcs.Task);

}

Thereareseveraloptionsthatwecanchoosetoimplementalimitonthedegreeofparallelism.Forexample,wecanusesemaphoresorothersynchronizationprimitives.However,wecanchoosemorecomfortableoptionstouseTaskParallelLibraryanditsPartitionertypetogetasetofpartitionswiththePartitioner.CreatePartitionermethodcall.Eachofthesepartitionsrepresentssomethinglikeaniteratorthatcanbeusedinparallelwithotherpartitions.Tostorethecompletedtasks,wewilluseanarrayofTaskCompletionSourceobjects,whichwillholdtheresultsincompletionorder.

Thewayofusingthismethodisshowninthefollowingexample:

varcities=newList<string>{"LosAngeles","Seattle","NewYork","San

Francisco"};

vartasks=cities.ForEachAsync(asynccity=>

{

returnnew{City=city,Weather=awaitGetWeatherForAsync(city)};

},2);

foreach(vartaskintasks)

{

vartaskResult=awaittask;

ProcessWeather(taskResult.City,taskResult.Weather);

}

Thesearetheresults:

[1:22:09PM]:Gettingtheweatherfor'LosAngeles'

[1:22:09PM]:Gettingtheweatherfor'Seattle'

Heretheparallelismlimitstartedtowork.Wewillnotrunmoretasksuntiloneofthemiscompleted:

[1:22:10PM]:Processingweatherfor'LosAngeles':'Temp:6C'

Thefirsttaskhasfinished;nowwecanrunonemoretask:

www.EBooksWorld.ir

Page 247: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

[1:22:10PM]:Gettingtheweatherfor'NewYork'

Thistaskiscompletedatonce:

[1:22:15PM]:Processingweatherfor'NewYork':'Temp:8C'

Here,werunonemoretask:

[1:22:15PM]:Gettingtheweatherfor'SanFrancisco'

Nowthesecondtaskiscompleted:

[1:22:16PM]:Processingweatherfor'Seattle':'Temp:7C'

Heregoesthelasttask:

[1:22:20PM]:Processingweatherfor'SanFrancisco':'Temp:4C'

Thisistheillustrationofthepreviousprocess:

Herewehavetwopartitions;eachoftheserunsasetoftasks.Thesecondpartitionisabletorunonlyonetask,becauseitrunsforalongtime.Thefirstpartitionmanagedtorunthreetasks.Thenumberofpartitionslimitsthedegreeofparallelism.

www.EBooksWorld.ir

Page 248: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SettingatasktimeoutOperationcancellationsupportisbuiltintotheTaskParallelLibrary;many.NETFrameworkclasses,aswellasthird-partycode,supportitandallowustoprovideacancellationmechanismincaseanoperationtimeouthappens.Someoftheseclassesmakeprogrammingeasierandallowyoutoprovidejustatimeoutvaluefortheoperation.

However,notallcodehasthispotential.Besidesthis,weoftenoperatewithataskthathasbeenalreadystartedandwecannotconfigurethetimeoutvalueintheoperation.Itisaverycommonproblemandthereisasolutionforthis:

publicstaticasyncTask<T>WithTimeout<T>(thisTask<T>task,

TimeSpantimeout)

{

//Covertwocornercases:whentaskiscompletedandwhen

//timeoutisinfinite

if(task.IsCompleted||timeout==Timeout.InfiniteTimeSpan)

{

returnawaittask;

}

varcts=newCancellationTokenSource();

if(awaitTask.WhenAny(task,Task.Delay(timeout,cts.Token))==task)

{

cts.Cancel();

returnawaittask;

}

//Observepotentialexceptionfromtheoriginaltask

task.ContinueWith(_=>{},

TaskContinuationOptions.ExecuteSynchronously);

thrownewTimeoutException();

}

NowwecanusetheWithTimeoutmethodonanytasktosetthetimeoutvaluefortheoperation.Wecanusethismethodlikethis:

try

{

Weatherweather=await

WeatherService.GetWeatherAsync("NewYork").

WithTimeout(TimeSpan.FromSeconds(2));

ProcessWeather(weather);

}

catch(TimeoutException)

{

Console.WriteLine("Taskwastimedout!");

}

Theimplementationlookssimple,butthereareacoupleofimportantnuances:

Inthebeginning,wecheckforsituationswherethetaskhasbeencompletedalready,

www.EBooksWorld.ir

Page 249: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

orwehaveaninfinitetimeoutvalue.Inthiscase,itisenoughtousetheC#awaitstatementtogetthetaskresultinasafemanner.Ifthetaskcompletesbeforethetimeout,wecancelthecorrespondingTask.Delaytimertask.Thislookslikeaslightoptimization,butitcanhaveanoticeableimpactontheapplicationperformance.Wetrytoobservetheprovidedtaskexception,whichisaveryimportantthingtodo.Ifwedonotdoso,wecouldeasilycauseTaskScheduler.TaskUnobservedExceptiontoberaised.In.NET4.5+,itwillnotruinyourapplicationatonce,butitshouldbeavoidedanyway.

www.EBooksWorld.ir

Page 250: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 251: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

AsynchronouspatternsSincereleasingthefirstversionofC#andthe.NETFramework,therehasbeenbuilt-insupportforrunningasynchronousoperations.Unfortunately,thisinfrastructurewasquitecomplicatedandhardtouse,andthiscausedthenextplatformversionstoincludenewways(patterns)ofwritingasynchronouscodethatenhancedasynchronousprogrammingexperience.

Here,wewillreviewthreeasynchronousprogrammingpatternsstartingfromtheoldest:

APM:AsynchronousProgrammingModel(introducedinthe.NETFramework1.0)EAP:Event-BasedAsynchronousPattern(releasedwiththe.NETFramework2.0)TAP:Task-BasedAsynchronousPattern(appearedwiththe.NETFramework4.0)

ThefirsttwopatternsareusuallyconsideredaslegacycodeandshouldbeusedonlyinsupportscenarioswherethereisnopossibilitytousethetaskinfrastructurefromTaskParallelLibrary.

www.EBooksWorld.ir

Page 252: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

AsynchronousProgrammingModelTheAsynchronousProgrammingModel(APM)structureisasfollows:

//Synchronousoperation

publicResultOperation(intinput,refintinOut,outintoutput);

//Firstmethodthatdenotesbeginningoftheasynchronous

//operation

publicIAsyncResultBeginOperation(intinput,refintinOut,outint

output,AsyncCallbackcallback,objectstate);

//Secondmethodthatshouldbecalledwhentheoperationis

//completed

publicResultEndOperation(refintinOut,outintoutput,IAsyncResult

asyncResult);

Thispatternisstructuredinthefollowingway:anasynchronousoperationsplitsintotwomethods—BeginOperationName/EndOperationName,wheretheOperationNamepartisanactualnameofthisoperation.TheBeginOperationNamemethodacceptsinputparameters,startsanasynchronousoperation,andreturnssomekindofoperationstatethatisrepresentedbyanobjectimplementingIAsyncResultinterface.Usually,italsoacceptsanadditionaloperationcontext—thestateparameter,andacallbackthatwillbecalledwhentheoperationcompletes.

Togettheoperationresultandoperationexceptionhandling,weneedtocalltheEndOperationNamemethod.Iftheoperationisalreadycomplete,thismethodwillimmediatelyreturntheresultorthrowanexception.Iftheoperationisstillrunning,thismethodcallwillbeblockeduntiltheoperationcompletes.

IAsyncResultprovidesaWaitHandleinstancethatcanbeusedtodeterminewhethertheoperationhasbeencompleted,orwhethertheoperationhascompletedsynchronously.

AsanexampleofusingtheAPMpattern,let’simplementtheweatherinformationservicecallandexplainthecodestepbystep:

publicclassWeatherService

{

privatereadonlyFunc<string,Weather>_getWeatherFunc;

publicWeatherService()

{

_getWeatherFunc=GetWeather;

}

publicWeatherGetWeather(stringcity)

{

//Originalsynchronousimplementation

}

publicIAsyncResultBeginGetWeather(stringcity,AsyncCallbackcallback,

objectstate){

return_getWeatherFunc.BeginInvoke(city,callback,state);

}

publicWeatherEndGetWeather(IAsyncResultasyncResult)

www.EBooksWorld.ir

Page 253: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

return_getWeatherFunc.EndInvoke(asyncResult);

}

}

Here,inthisexample,wesimulatedanasynchronousoperationwithanasynchronousdelegateinvocation.TherealAPMimplementationincludingremoteservicecalldetailsistoocomplicated,anditdoesnotmakesensetoillustratetheAPMpattern.

Then,wewillwriteaclientwithAPM:

varweatherServce=newWeatherService();

//Pseudoasynchronouscall

stringnewYork="NewYork";

IAsyncResultar1=weatherServce.BeginGetWeather(newYork,callback:null,

state:null);

ar1.AsyncWaitHandle.WaitOne();

Weatherweather1=weatherServce.EndGetWeather(ar1);

ProcessWeather(newYork,weather1);

//Realasynchronousversion

stringseattle="Seattle";

weatherServce.BeginGetWeather(seattle,callback:(IAsyncResultasyncResult)

=>

{

varcontext=(Tuple<string,WeatherService>)asyncResult.AsyncState;

try

{

Weatherweather=context.Item2.EndGetWeather(asyncResult);

ProcessWeather(context.Item1,weather);

}

catch(Exceptione)

{

HandleWeatherError(e);

}

},

state:Tuple.Create(seattle,weatherServce));

ThefirstpieceofcodeshowshowwecancallanasynchronousoperationintheAPMparadigm.WestartedwiththeBeginGetWeathermethodcall,thenimmediatelycalledar1.WaitHandle.WaitOne.WecansimplycallweatherService.EndGetWeatherinsteadandgetthesameresult.

Thenweusedarealasynchronousoperationcall.WehaveusedboththelastinputparametersoftheBeginGetWeathermethod—callbackandstate.Noticethatthereisnocontextcapture—thecontextgetsintoasynchronousoperationthroughastateparameter.

TheAPMpatternhasthefollowingfeatures:

Low-levelpattern:Thiswasintroducedinthefirst.NETFrameworkversionandisusedformanyasynchronousoperationsintheBaseClassLibrary.Lowperformanceoverhead:Thecallbackmethodiscalledonthesamethreadwheretheasynchronousoperationcompleted.Noadditionaloperationsfor

www.EBooksWorld.ir

Page 254: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

synchronizationcontextcapturingoccurs.

Besides,itisveryhardtocombineseveralasynchronousoperations,soonedependsonanother.

Couplingbetweentheasynchronousoperationprovideranditsconsumers:Asynchronousoperationisnotafirst-classobject.Itisnotpossibletoinitiatetheoperation,thentopassitsomehowtotheothercodeandhandleitthere.Aclassthatprovidesanasynchronousoperationanditsclientclasseshaveatightconnection.This,aswell,makesunittestingforsuchoperationsveryhard,sinceitisveryhardtocreateamockasynchronousoperation.

TheAPMcanbeusedinthefollowingscenario—onlyforlegacycodesupport.Task-basedasynchronouspatternscandoeverythingAPMcando.Alsotheyhavealowperformanceoverhead,butaremodernandeasytouse—especiallywiththeC#async/awaitstatements.

www.EBooksWorld.ir

Page 255: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Event-basedAsynchronousPatternAnEvent-basedAsynchronousPattern(EAP)structureisasfollows:

//Synchronousoperation

publicResultOperation(intinput,refintinOut,outintoutput);

//RaisedwhentheOperationfinished(successfuly,withexceptionorwas

cannelled)

publiceventEventHandler<OperationCompletedEventArgs>OperationCompleted;

//Reportexecutionprogress

publiceventEventHandler<ProgressChangedEventArgs>

OperationProgressChanged;

//Methodthatstartsasynchronousexecution

publicvoidOperationAsync(intinput,refintinOut);

//Methodthatstartsasynchronousexecutionandgetsadditionaluser

definedstate

publicvoidOperationAsync(intinput,refintinOut,objectuserState);

//Cancelpendingoperation

publicvoidCancelAsync(objectstate);

publicclassOperationCompletedEventArgs:AsyncCompletedEventArgs

{

publicOperationCompletedEventArgs(

Exceptionerror,boolcancelled,objectuserState)

:base(error,cancelled,userState)

{

}

publicResultResult{get;internalset;}

publicintInOut{get;internalset;}

publicintOutput{get;internalset;}

}

EAPwasimplementedin.NETFramework2.0andwasdesignedtobeusedinapplicationUIcomponents.Mostofthe.NETtypesthatimplementthispatterninherittheSystem.ComponentModel.ComponentclassaswellandcanbeeasilyusedwithWindowsFormsorWPFdesign-timeeditor.

ThemainideabehindEAPistouseeventsfornotificationaboutasynchronousoperationcompletion.WestarttheoperationwiththeOperationNameAsyncmethod,andthecompletioneventnameisusuallyOperationNameCompleted.Besidesthis,thereareotherevents,forexampletheOperationProgressChangedeventthatallowsustotracktheoperation’sexecutionprogress.

Animportantfeatureofthispatternisthattheseeventsusethesamesynchronizationcontextwheretheasynchronousoperationhasbeenstarted.IfweusetheUIthreadtorunthisoperation,thenitispossibletouseUIcontrolsfromtheeventhandlersmethodofthecomponentthatimplementsEAP,whichmakesthecodecleanandcomfortabletowrite.

Let’simplementaweatherinformationservicewithEAP:www.EBooksWorld.ir

Page 256: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

publicclassWeatherService

{

privatebool_isOperationRunning=false;

privatereadonlySendOrPostCallback_operationFinished;

publicWeatherService()

{

//Thisdelegateshouldbecalled

//incapturedsynccontext

_operationFinished=ProcessOperationFinished;

}

publicWeatherGetWeather(stringcity)

{

//Originalsynchronousimplementation

}

publiceventEventHandler<GetWeatherCompletedEventArgs>

GetWeatherCompleted;

publicvoidGetWeatherAsync(stringcity,objectuserState)

{

if(_isOperationRunning)

thrownewInvalidOperationException();

_isOperationRunning=true;

AsyncOperationoperation=AsyncOperationManager

.CreateOperation(userState);

//RunningGetWeatherasynchronously

ThreadPool.QueueUserWorkItem(state=>

{

GetWeatherCompletedEventArgsargs=null;

try

{

varweather=GetWeather(city);

args=newGetWeatherCompletedEventArgs(weather,state);

}

catch(Exceptione)

{

args=newGetWeatherCompletedEventArgs(e,state);

}

//UsingAsyncOperationthatwillmarshalcontrol

//flowtothesynchronizationcontextthatwas

//capturedatthebeginningofthismethod.

operation.PostOperationCompleted(_operationFinished,args);

},userState);

}

privatevoidProcessOperationFinished(objectstate)

{

//Markthatcurrentoperationiscompleted

www.EBooksWorld.ir

Page 257: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

_isOperationRunning=false;

varargs=(GetWeatherCompletedEventArgs)state;

varhandler=GetWeatherCompleted;

if(handler!=null)

handler(this,args);

}

}

TheGetWeatherAsyncmethodcontainsthemainpatternlogic.First,wecreatedtheAsyncOperationobject,wherewecapturedthecurrentsynchronizationcontextwithSynchronizationContext.Current.Then,weusedtheThreadPool.QueueUserWorkItemmethodtoruntheGetWeatheroperationasynchronouslyonathreadpool.Thenweusedoperation.PostOperationCompletedtopostanotificationaboutoperationcompletiononthecapturedsynchronizationcontext.ThiswillalloweventsubscriberstohandletheGetWeatherCompletedeventsafelyandwillmakeitpossibletouseUIcontrolswithoutusingtheControl.InvokeandDispatcher.BeginInvokemechanics.

Nowlet’slookathowtousetheservicewithEAP:

varweatherService=newWeatherService();

varcity="NewYork";

//Startasynchronousoperation

weatherService.GetWeatherAsync(city,userState:null);

//IfcurrentmethodisrunninginUIthread

//followingeventhandlerwouldbeexecutedintheUIthread

weatherService.GetWeatherCompleted+=(sender,args)=>{

Weatherresult=args.Result;

ProcessWeather(city,result);

};

TheEAPfeaturesareasfollows:

High-levelpattern:EAPallowsustoconsumeasynchronousoperationswitheaseaswellasstartnewonesHighoverhead:Sinceoperationcompletioneventsalwaysgetpostedtothecapturedsynchronizationcontext,thispatternisnotintendedtobeusedfromlow-levelcomponentsthatdointensiveIOoperationsIntendedforUIcomponents:SinceEAPwasdesignedforaveryspecificscenario(UIcomponents),itmightnotbethebestchoicetoprogramsomeotherfeaturesComplicatedimplementation:WhilethisisdefinitelyeasierthanAPM,itisstillhardtoprogramreal-worldscenarioswithoperationprogressandcancellationCouplingbetweenasynchronousoperationprovideranditsconsumers:Similartothepreviouspattern,thisonealsocreatestightcouplingbetweentheoperationclassandclientclasses

EAPcanusedinthefollowingscenario—legacycodesupport.Nevertheless,ifyouwriteanewcodeyoushouldnotuseEAP.Task-basedasyncpatternhaseverythingthatEAPhas,butitalsohaslanguagelevelsupport,loosecoupling,andalotofotherusefulfeatures.

www.EBooksWorld.ir

Page 258: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Task-basedAsynchronousPatternTaskParallelLibraryhasbeenexistingsincethe.NETFramework4.0release,anditintroducedanewasynchronousprogrammingpattern—Task-basedAsynchronousPattern(TAP).Thispatternconsistsofthefollowingmethods:

//Synchronousoperation

publicResultOperation(intinput,refintinOut,outintoutput);

//Asynchronousversion

publicTask<WrappedResult>OperationAsync(intinput,intinOut);

//Customresultthatwrapsin/outandoutparameters

publicclassWrappedResult

{

publicResultResult{get;internalset;}

publicintInOut{get;internalset;}

publicintOutput{get;internalset;}

}

SimilartoEAP,TAPusesthesamenamingscheme.OperationsarenamedOperationNameAsyncbyaddingtheAsyncsuffixtothesynchronousimplementationname.However,themainideabehindTAPistouseaspecialTaskobjectthatrepresentsanasynchronousoperationwithoutanyreturnvalue,andTask<T>forthosethatreturnresultsoftheTtype.SincewecanaccesstheresultonlythroughtheTask.Resultproperty,everyinputandoutputparametermustbeapartofthereturnvalue.

Thefollowingisonemoreweatherinformationserviceimplementation:

publicclassWeatherService

{

publicWeatherGetWeather(stringcity)

{

//Originalsynchronousimplementation

}

publicTask<Weather>GetWeatherAsync(stringcity)

{

returnTask.Run(()=>GetWeather(city));

}

}

TheeasiestimplementationthatseemstobeobviousistowrapasynchronousmethodintoataskwiththehelpoftheTask.Runmethod.However,thisapproachshouldnotbeusedinreal-worldapplications,unlessyouarecompletelysureaboutwhatisgoingon.

NoteThisantipatterniscalled“asyncoversync”andusingthiswillleadtoscalabilityandperformanceproblemsinyourapplication.Mostofthetrulyasynchronousoperationsinthe.NETFrameworkareIO-bound,andthusdonotrequireusingadditionalthreads.ThistopicwillbereviewedindetailinChapter8,Server-SideAsynchrony.

Let’slookatthefollowingcode:

publicasyncTaskProcessWeatherFromWeatherService()

www.EBooksWorld.ir

Page 259: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

varweatherService=newWeatherService();

stringcity="SanFrancisco";

//IfthismethodwascalledintheUIthread,

//"awaiter"willcapturesynchronizationcontext

//andProcessWeathermethodwouldbecalledintheUIthreadas

//well

Weatherweather=awaitweatherService.GetWeatherAsync(city);

ProcessWeather(city,weather);

}

Task-basedAsynchronousPatternisnowthemostpopularandmostconvenientwaytodevelopasynchronousapplications.Itcanbecharacterizedbythefollowing:

Lowoverhead:Taskshavelowoverheadandcanbeusedinhigh-loadscenarios.High-level:TaskisahighlevelabstractionthatprovidesaconvenientAPItocombineasynchronousoperations,tocaptureornotcapturethecurrentsynchronizationcontextifneeded,convertolderAPMandEAPpatternstoTAP,andmanyotherfeatures.Comfortabletouse:Thispatterniseasytousebydevelopers,butatthesametime,ithasarichAPIandmorefeaturesthantheprevioustwo.LanguagesupportinC#/VB:C#andVB.NEThasbuilt-inasync/awaitstatementsthatmakeasynchronousprogrammingmucheasier.ThisinfrastructureisbasedontheTaskandTask<T>types.

NoteAswesawinChapter5,C#LanguageSupportforAsynchrony,awaitcanbeusedwithanytypethathasitsownmethodoranextensionmethodcalledGetAwaiterwithoutparameters,whichreturnstheobjectthatimplementstheINotifyCompletioninterfaceandcontainstheIsCompletedBooleanpropertyandtheGetResultmethodwithnoparameters.

TaskandTask<T>arefirst-classobjects:Unlikepreviouspatterns,ataskinstanceisself-sufficient.Itcanbepassedasaparametertoothermethodsorcanbestoredinavariableorinstancefield.Ifyouhaveaccesstothetaskinstance,youwillhavefullcontroloverthecorrespondingasynchronousoperation.Wedonotneedtousetheasynchronousoperationclassasecondtimetofinishtheoperation.Wecantestsuchoperationsandreturnanalreadycompletedtaskfromamockmethod.Gettingridofsideeffects:UsingtheTask<T>classencouragestheavoidanceofsideeffectsintheprogram,whichisveryimportanttoreducecontentionandimprovescalability.

www.EBooksWorld.ir

Page 260: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 261: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ConcurrentpatternsWehavealreadyreviewedsomeofthesepatternsearlierinthisbook.Forexample,inChapter4,TaskParallelLibraryinDepth,westudiedParallel.InvokeandParallel.Foreach,whichactuallyisanimplementationofthefork/joinpattern.InChapter6,UsingConcurrentDataStructures,wereviewedaProducer/Consumerpatternimplementation.However,thereisaveryimportantscenariothatwehavenotseenyet.Itiscalledaparallelpipeline.

www.EBooksWorld.ir

Page 262: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ParallelpipelinesUsuallyacomplexparallelcomputationcanbeconsideredasseveralstagescombinedintosomesortofapipeline.Thelatterstageneedstheresultsoftheformer,andthispreventsthesestagesfromrunninginparallel.However,thecalculationsinsideeachstagecanbeindependent,whichallowsustoparallelizeeachstageitself.Besidesthis,wecansimultaneouslyrunallthestages,assumingthatwecanprocessstageresultsonebyone,sowedonothavetowaituntileachstagecomputesalltheresultsbeforeproceedingtothenextstage.Insteadofthis,wegetanitemfromapreviousstageassoonasitisreadyandpassitalongtothenextstage,andsoonandsoforth,untilthefinalstage.Thiswayoforganizingparallelcomputationsisknownasparallelpipeline,whichisaspecialcaseofaProducer/Consumerpattern.Itallowsustoachievealmostparallelprocessingofstagecomputations,shiftedbythetimethatisrequiredtogetthefirststageresult.

ThefollowingcodeshowshowtoimplementaparallelpipelineusingastandardBlockingCollectiondatastructure:

privateconstintParallelismDegree=4;

privateconstintCount=1;

staticvoidMain(string[]args){

varcts=newCancellationTokenSource();

Task.Run(()=>{

if(Console.ReadKey().KeyChar=='c'){

cts.Cancel();

}

});

varsourceArrays=newBlockingCollection<string>[ParallelismDegree];

for(inti=0;i<sourceArrays.Length;i++){

sourceArrays[i]=newBlockingCollection<string>(Count);

}

vargetWeatherStep=newPipelineWorkerAsync<string,Weather>(

sourceArrays,

city=>WeatherService.GetWeatherAsync(city),

cts.Token,

"GetWeather",

Count

);

varconvertTempStep=newPipelineWorkerAsync<Weather,Tuple<string,

decimal>>(

getWeatherStep.Output,

weather=>Task.FromResult(Tuple.Create(weather.City,

weather.TemperatureCelcius*(decimal)9/5+32)),

cts.Token,

"ConvertTemperature",

Count

);

varprintInfoStep=newPipelineWorkerAsync<Tuple<string,decimal>,

string>(

www.EBooksWorld.ir

Page 263: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

convertTempStep.Output,

t=>Console.WriteLine("Thetemperaturein{0}is{1}Fonthreadid

{2}",t.Item1,t.Item2,Thread.CurrentThread.ManagedThreadId),

cts.Token,

"PrintInformation"

);

try{

Parallel.Invoke(

()=>{

Parallel.ForEach(

new[]{"Seattle","NewYork","LosAngeles","SanFrancisco"},

(city,state)=>{

if(cts.Token.IsCancellationRequested){

state.Stop();

}

AddCityToSourceCollection(sourceArrays,city,cts.Token);

});

foreach(vararrinsourceArrays){

arr.CompleteAdding();

}

},

()=>getWeatherStep.RunAsync().GetAwaiter().GetResult(),

()=>convertTempStep.RunAsync().GetAwaiter().GetResult(),

()=>printInfoStep.RunAsync().GetAwaiter().GetResult()

);

}

catch(AggregateExceptionae){

foreach(varexinae.InnerExceptions)

Console.WriteLine(ex.Message+ex.StackTrace);

}

if(cts.Token.IsCancellationRequested){

Console.WriteLine("Operationhasbeencanceled!PressENTERtoexit.");

}

else{

Console.WriteLine("PressENTERtoexit.");

}

Console.ReadLine();

}

staticvoidAddCityToSourceCollection

BlockingCollection<string>[]cities,stringcity,

CancellationTokentoken){

BlockingCollection<string>.TryAddToAny(cities,city,50,token);

Console.WriteLine("Added{0}tofetchweatheronthreadid{1}",city,

Thread.CurrentThread.ManagedThreadId);

Thread.Sleep(TimeSpan.FromMilliseconds(100));

}

Atthebeginningoftheprecedingcode,weareimplementingacancellationoperationforthepipelinebyrunningaseparatetaskthatislisteningfortheCkeypress.WhentheuserpressestheCbutton,thetaskrunscts.Cancelthatsignalsacancellationoperationtothesharedcancellationtoken.Thistokengoesintoallthefurtheroperationsandisableto

www.EBooksWorld.ir

Page 264: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

canceltheentireparallelpipelineatonce.

Now,wedefinethepipelinebehavior.First,wesettheparallelismdegreeforourparallelpipeline.Inthefollowingexample,wewillcreatefourblockingcollectionsforoneelementeach.Itwillcausefourelementstobeprocessedinparallel.Ifweneedtochangethis,wecanusetwocollectionsfortwoelements,andsoon.

Next,wewilldefinepipelinesteps.Thefirststepisresponsibleforgettingweatherinformationforeachcitythatappearsinthesourcecollection.ThenthenextstepwillconvertthetemperaturefromCelsiustoFahrenheit.Thefinalstepwillprintouttheweatherinformationtotheconsole.

Allweneedtodonowisruntheentirepipeline.WewillusetheParallel.Invokestatementtorunallthepipelinestagesinparallel,andinthefirststage,wewilluseParallel.Foreachtofillinthecitiescollectioninparallelaswell:

classPipelineWorkerAsync<TInput,TOutput>

{

Func<TInput,Task<TOutput>>_processorAsync=null;

Action<TInput>_outputProcessor=null;

BlockingCollection<TInput>[]_input;

CancellationToken_token;

privateint_count;

publicPipelineWorkerAsync(

BlockingCollection<TInput>[]input,

Func<TInput,Task<TOutput>>processorAsync,

CancellationTokentoken,

stringname,

intcount)

{

_input=input;

_count=count;

_processorAsync=processorAsync;

_token=token;

Output=newBlockingCollection<TOutput>[_input.Length];

for(inti=0;i<Output.Length;i++)

Output[i]=null==input[i]?null:newBlockingCollection

<TOutput>(Count);

Name=name;

}

publicPipelineWorkerAsync(

BlockingCollection<TInput>[]input,

Action<TInput>renderer,

CancellationTokentoken,

stringname){

_input=input;

_outputProcessor=renderer;

_token=token;

Name=name;

Output=null;

}

www.EBooksWorld.ir

Page 265: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

publicBlockingCollection<TOutput>[]

Output{get;privateset;}

publicstringName{get;privateset;}

publicasyncTaskRunAsync(){

Console.WriteLine("{0}isrunning",this.Name);

List<Task>tasks=newList<Task>();

foreach(varbcin_input){

varlocal=bc;

vart=Task.Run(newFunc<Task>(async()=>{

TInputreceivedItem;

while(!local.IsCompleted&&!_token.IsCancellationRequested){

varok=local.TryTake(outreceivedItem,50,_token);

if(ok){

if(Output!=null){

TOutputoutputItem=await_processorAsync(receivedItem);

BlockingCollection<TOutput>.AddToAny(Output,outputItem);

Console.WriteLine("{0}sent{1}tonext,onthreadid

{2}",Name,outputItem,Thread.CurrentThread.ManagedThreadId);

Thread.Sleep(TimeSpan.FromMilliseconds(100));

}

else{

_outputProcessor(receivedItem);

}

}

else{

Thread.Sleep(TimeSpan.FromMilliseconds(50));

}

}

}),

_token);

tasks.Add(t);

}

awaitTask.WhenAll(tasks);

if(Output!=null){

foreach(varbcinOutput)bc.CompleteAdding();

}

}

}

ThepipelinesteplogicisdefinedinsidethePipelineWorkerAsyncclass.Wehavecreatedtheworkerinstance,providingitwiththeinputcollectionsandatransformationfunctionthatgetsaninitialvalueandcalculatestheresult.Thenwerancollectionprocessinginparallel.Whileweprocessedeachcollection,wepassedcalculationresultstotheoutputcollectionsofthenextstepinourpipeline.Thishappensuntilthefinalstephasbeenreached,whichjustprintsresultstotheconsole.

www.EBooksWorld.ir

Page 266: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 267: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,wehaveconsideredthedifferentkindsofasynchronousprogrammingpatterns—fromthesmallestonessuchasataskwithtimeouttothelargemultipurposeparallelpipelinepattern.Wehavereviewedthehistoryofasynchronousprogramminginthe.NETFrameworkandC#,andwentstepbystepthroughallexistingpatternsincludingAPM,EAP,andTAP.

Inthenextchapter,wewillcoveraveryimportanttopicofserver-sideasynchronousprogramming.Wewilllearnaboutscalability,performancemetrics,detailsofIO-boundandCPU-boundasynchronousoperations,andhowtheslightestmistakecanruinyourbackend.Also,wewilllearnacoupleoftricksthatwillallowustodetectpossiblescalabilityproblemsandavoidthem.

www.EBooksWorld.ir

Page 268: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 269: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter8.Server-sideAsynchronyInthischapter,wewillshowhowaserverapplicationisdifferentfromotherapplications,whatscalabilityis,andhowitisimportant.Wewilllookatthe.NETHTTPAPIserverapplicationframework,learntouseVisualStudiotocreateloadtests,digintoasynchronousI/Odetails,andreviewimportantnuancessuchassynchronizationcontext.Finally,wewillsuggestanarchitecturalpatternforaserverapplicationtorunlongoperationsandremainscalableandperformant.

www.EBooksWorld.ir

Page 270: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ServerapplicationsAserverapplicationcanbedefinedasanapplicationthatacceptsrequests,processesthem,andsendsthecorrespondingresponsestotheclient.Communicationhappensviasometransportprotocols,andusually,butnotnecessarily,theclientandserverapplicationsaresituatedondifferentphysicalcomputers.Thecomputerthatrunstheserverapplicationisusuallyreferredtoastheserver.

Therearemanytypesofserverapplications.Forexample,aRemoteDesktopServicessoftwarethatallowsustoopenremotesessiontoaWindowsmachineisaserverapplication.Eachuserconnectionconsumesalotofserverresources,butinthisparticularscenario,thisisinevitable.Thisserverapplicationdoesnotneedtosupporthundredsorthousandsofsimultaneoususersandisintendedtobelikethis.However,ifweimagineawebsitethatallowsonlyafewuserstobrowseitsimultaneously,itwouldbedefinitelyafailure.

Ontheotherhand,itisOKwhenawebsiteusergetsnotificationsfromtheserverwithadelayof2-3seconds,butifwetrytoworkwitharemotedesktopconnectionthatshowsupdatesfromtheserverwithsuchadelay,itwouldbeveryuncomfortable.Therearedifferentmetricsthatcharacterizeaserverapplication,andindifferentscenariosdifferentmetricsareimportant.Oneofthemostimportantserverapplicationcharacteristicsisscalability.HereishowthistermisdefinedinWikipedia:

Scalabilityistheabilityofasystem,network,orprocesstohandleagrowingamountofworkinacapablemanneroritsabilitytobeenlargedtoaccommodatethatgrowth.

Imaginethatwehaveawebsiteandithandlesacertainnumberofconcurrentusers.Tohandlemoreusers,wecantrytoaddmorememoryandmaybeinstallanewCPUwithmorecorestotheserver.Ifthisallowsustoachievethisgoal,wecansaythattheapplicationisabletoscalevertically.Ifwecaninstallmoreserversandmakeourapplicationrunonmultiplemachinesandhandlemoreusers,thiskindofscalabilityiscalledhorizontalscalability.Thefollowingdiagramshowstheverticalandhorizontalscalability:

www.EBooksWorld.ir

Page 271: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Itmayseemthateveryserverapplicationshouldscaleinbothways,butusuallythisisnotwhathappens.Thistopicisveryinterestingandvast,anditisworthwritinganotherbookonthis.Let’sstatethatmostgeneral-purposeserverapplicationsnowadaysarewebapplicationsandservices,solaterwewillreviewtheASP.NETwebplatformandspecificallytheOWINWebAPIframework.

www.EBooksWorld.ir

Page 272: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 273: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TheOWINWebAPIframeworkInthischapter,wewillconcentrateontheASP.NETplatform.Atthetimeofwritingthisbook,ASP.NET5wasnotreleased.However,theOWINprojectexisted,andthecodelookedalmostthesameasinASP.NET5.Sothiswasusedtowritethesampleserverapplications.WhenASP.NET5willbereleased,itwillbeeasytoconvertthiscodetothenewplatform.WewillnotgointothedetailsofOWIN;itisanacronymforOpenWebInterfacefor.NET,andbasically,itisawaytocomposeapplicationcomponentswitheachother.ItisapartoftheASP.NETecosystem,andallweneedtoknowfornowisthatwithOWINwecanwriteHTTPservices.

WhenweuseASP.NET,weseeatypicalHTTPapplicationplatform.First,thereisanHTTPhostthatacceptsincomingconnectionsfromclients.ItcanbeafullInternetInformationServiceswebserver,oritcanbeasimpleHTTPlistenerhostedinausual.NETprocess.AftertheincomingHTTPrequestisprocessedbytheHTTPhost,itgoestotheASP.NETinfrastructure.Itgetsaworkerthreadfromthe.NETthreadpoolandstartsrequestdataprocessingonthisthread.

First,ittriestodefinewhatcodewillbehandlingthisrequestbymatchingtherequestURLtoexistingroutes.AroutedescribeshowURLpartscorrelatetowebapplicationcodeparts.Alogicalsetofservercodeiscalledacontroller.IntheOWINWebAPIframework,acontrollercontainsanumberofactions—methodsthathandledifferentHTTPrequestsusuallybyHTTPverbs(orbyotherrulesthatcanbesetinroutes).Beforeallthisbecomestoocomplicated,let’slookatthecode.Inthesamplesdirectory,itislocatedintheChapter8solutionfolderintheAsyncServerproject.ToleverageOWIN,weneedtoinstalltheMicrosoft.AspNet.WebApi.OwinSelfHostNuGetpackage.ThefirstpartistheentrycodefortheentireOWINapplication:

publicclassStartup

{

publicvoidConfiguration(IAppBuilderappBuilder)

{

varconfig=newHttpConfiguration();

config.Routes.MapHttpRoute(

"DefaultApi",

"api/{controller}/{id}",

new{id=RouteParameter.Optional});

appBuilder.UseWebApi(config);

}

}

HerewehaveconfiguredourOWINapplicationbyprovidingadefaultroute.ItwillmatchURLssuchashttp://hostname/api/somename/5toaclasscalledSomenameControllerthatcontainstheGetmethod(iftherequestverbwasHTTPGET)andwillcallthismethodprovidingaparameterid=5intoit.ThelastlineinstructsOWINtouseaWebAPIcomponentintheapplication.

Nowlet’slookatthecontroller:

publicclassBadAsyncController:ApiController

www.EBooksWorld.ir

Page 274: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

privatereadonlyAsyncLib_client;

publicBadAsyncController()

{

_client=newAsyncLib();

}

publicasyncTask<HttpResponseMessage>Get()

{

varsw=Stopwatch.StartNew();

stringvalue=await_client.BadMethodAsync();

sw.Stop();

vartimespan=sw.Elapsed;

returnRequest.CreateResponse(HttpStatusCode.OK,

new

{

Message=value,

Time=timespan

});

}

}

Here,weseetheGetmethodcode,whichcallsalibrary’sasynchronousmethodandmeasuresthetimeittooktocomplete.Thenitreturnsananonymousobjectcontainingtheresponsedata.ItwillbeserializedtotheJSONformatbydefault.

Wewilldefineanothercontroller,whichwillbedifferentonlywithrespecttoanasynchronouslibrary’smethodnamethatitcalls:

publicclassGoodAsyncController:ApiController

{

privatereadonlyAsyncLib_client;

publicGoodAsyncController()

{

_client=newAsyncLib();

}

publicasyncTask<HttpResponseMessage>Get()

{

varsw=Stopwatch.StartNew();

stringvalue=await_client.GoodMethodAsync();

sw.Stop();

vartimespan=sw.Elapsed;

returnRequest.CreateResponse(HttpStatusCode.OK,

new

{

Message=value,

Time=timespan

});

}

}

HerewehavecalledGoodMethodAsync.Wewilldescribebothcontrollerslater,butnowweneedtoruntheapplication.Weneedtocreateanapplicationhost:

www.EBooksWorld.ir

Page 275: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

classProgram

{

staticvoidMain(string[]args)

{

stringbaseAddress="http://localhost:9000/";

using(WebApp.Start<Startup>(url:baseAddress))

{

HttpClientclient=newHttpClient();

varresponse=client.GetAsync(baseAddress+"api/GoodAsync").Result;

Console.WriteLine(response);

Console.WriteLine(response.Content.ReadAsStringAsync().Result);

Console.WriteLine();

response=client.GetAsync(baseAddress+"api/BadAsync").Result;

Console.WriteLine(response);

Console.WriteLine(response.Content.ReadAsStringAsync().Result);

Console.ReadLine();

}

}

}

Thiscodestartsawebapplicationonalocalhostonport9000.Ifthisportisalreadytaken,therewillbeanexception.Inthiscase,justchangetheportnumber.Aftertheapplicationstarts,wewillissuetwoHTTPrequestsandseetheresultsontheconsolewindow.Bothrequestsshouldcompletewithoutanyissuesandshowthatittooktwosecondsforthemtocomplete.Youcanusearegularwebbrowsertoopenhttp://localhost:9000andseetheresults.InternetExplorerisnotverygoodwithJSON,butGoogleChromewillshowyouaJSONresultwithgoodformatting.Besidesthis,thereisaveryusefulGoogleChromeextensioncalledPostman.ThiscanissuedifferentHTTPrequestsandisverycomfortabletouse;itisshowninthefollowingscreenshot:

www.EBooksWorld.ir

Page 276: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 277: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 278: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

LoadtestingandscalabilityWeseethatboththecontrollershavebehavedthesamesofar.However,whataboutscalability?Tocheckhowwelltheapplicationscales,weneedtohavemanyrequestsfrommanyusers.Wewillbeabletodothiswiththehelpofdifferenttools.First,wecanuseVisualStudio,butitrequirestheUltimateedition(ortheEnterpriseeditionforVisualStudio2015)wherewebtesttoolsareavailable.Ifyouhaveit,thenyoucancreateanewprojectandchoosetheWebPerformanceandLoadTestprojectfromthetestcategory.Inthesamplesfolder,thereisanalreadycreatedtestprojectthatiscalledAsyncServerTests.NowweneedtocreateWebPerformanceTest.Aftercreating,itwillruninthebrowserandtrytorecordyourtest.Youcanrecorditfromthebrowserorstoptherecordingandaddanewrequestasshowninthefollowingscreenshot;theninPropertiesprovideafullURLtoseewhathavewetestedsofar:

Next,weneedtocreatealoadtest.Whenweaddanewloadtest,itwillshowawizardwithdifferentoptions.Weneedtochooseaconstantloadpatternandsetthenumberofusersto1000.Then,inthetestmix,wehavetopickawebtestthatwehavejustcreated.Finally,intherunsettingssetthewarm-updurationto15seconds,andsetdurationtimeto2minutes.ClickonFinishandrepeatallthisforanothercontroller.Wheneverythingisset,let’srunaloadtestforGoodAsyncController.Theoutputisasshowninthefollowingscreenshot:

www.EBooksWorld.ir

Page 279: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thesedatashowthatfor1000userspersecond,theaverageresponsetimewasstillabouttwoseconds.ThisisaverygoodresultandshowsthatGoodAsyncControllerscaleswellandisabletohandlemanyconcurrentrequests.TocomparethistoBadAsyncController,weneedtocreateawebandloadtestforthis,andthenruntheloadtest.

Beforedoingso,ifwedonothavetheVisualStudioUltimateedition,itisstillpossibletoloadtestourwebapplication.TheeasiestwayistousetheApachebenchcommandlinetool.ItisincludedintheApachewebserverinstallation,butifyoudonotneedit,youcandownloadxampp(apreconfiguredApachedistributive)thathasaportableinstallationoption.Thismeansthatyoucandownloadaziparchivefromthexamppsite,andthenextractittosomewhereinyourfilesystem.Youwillfindtheab.exetoolinthexampp\apache\binfolder.Ithasmanyparameters,butwecanusejusttwoofthem—thenumberofconcurrentrequestsandthetimeforthebenchmark.Herewehaveissued1,000concurrentrequestsfor2minutestoourGoodAsyncController:

ab-c1000-t120http://localhost:9000/api/GoodAsync

Theoutputshownwillbethesame—theaveragerequesttimewillbearound2seconds.

Nowlet’sseetheperformancetestresultsforBadAsyncController.Thefollowingscreenshotshowstheperformancetestresults:

www.EBooksWorld.ir

Page 280: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Herethepictureisdifferent.Weseethattheaveragerequesttimeismorethantentimeshigherthanintheprevioustest.Obviously,thiscodedoesnotscaleaswellasGoodAsyncControllerdoes.Sincethecodesinsidethecontrollersareidentical,andtheonlydifferenceistheasynchronouslibrarymethodthatwascalled,itmakessensetolookintothislibraryandseewhatisgoingon:

publicclassAsyncLib

{

publicasyncTask<string>GoodMethodAsync()

{

awaitTask.Delay(TimeSpan.FromSeconds(2));

return"Goodasynclibrarymethodresult";

}

publicasyncTask<string>BadMethodAsync()

{

Thread.Sleep(TimeSpan.FromSeconds(2));

return"Badasynclibrarymethodresult";

}

}

Thecodeisactuallyverysimple.Boththemethodswaitfortwoseconds,andthenreturnstringresults.However,wecanseethatThread.Sleepisobviouslythereasonbehindbadscalability.Inthediagram,youcanseewhatisgoingonwhenweuseBadMethodAsyncinourwebapplication:

www.EBooksWorld.ir

Page 281: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Eachworkerthreadstartsrunningourcodeandwaitstwosecondsdoingnothing.Then,theyreturntheresponse.Aswemayrecallfromthepreviouschapters,threadpoolworkerthreadsarealimitedresource,andwhenwestartissuing1,000concurrentrequestsinashorttime,alltheworkerthreadsbecomeoccupiedrunningThread.Sleep.Atthesametime,GoodAsyncControllerbehavesdifferently.Thiscanbeseeninthefollowingdiagram:

Task.Delayusesatimerobjectunderthehood.ThisallowsanASP.NETworkerthreadtostartawaitoperation,andthentoreturntotheapplicationpoolandprocesssomeother

www.EBooksWorld.ir

Page 282: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

requests.Whentwosecondspass,thetimerpostsacontinuationcallbacktoanavailableASP.NETthreadpoolworkerthread.Thisallowstheapplicationtoprocessmoreuserrequests,sinceworkerthreadsarenotblocked.So,thistimerobjecthelpsourapplicationtoremainfastandscalable.

www.EBooksWorld.ir

Page 283: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 284: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

I/OandCPU-boundtasksIfweconsideranyCPU-intensiveworkthatourserverapplicationcanruninsteadofThread.Sleep,wewillfindthatthisapplicationwillsufferfromthesameproblem.Workerthreadswillbecomebusyquitequickly,andthereisnotmuchthatwecandoaboutthis.Wecantrytochangeourapplicationlogictoworkaroundthisproblem,andwewillgetbacktothisproblemattheendofthechapter.

However,besidesCPU-boundoperations,therearetasksrelatedtoinput/outputprocesses,suchasreadingorwritingafile,issuinganetworkrequest,orevenperformingaqueryagainstadatabase.TheseoperationsusuallytakemuchmoretimecomparedtoCPU-boundwork,andpotentiallytheyshouldbemoreproblematictoourserverapplication.I/O-boundworkcantakeseconds.Sodoesthismeanthatourworkerthreadswillbelockedforalongertimeandtheapplicationwillfailtoscale?

Fortunately,thereisonemorecomponentoftheI/O-boundoperation.Whenwementionafileornetworkrequest,weknowthattherearephysicaldevicessuchasdisksandnetworkcardsthatactuallyexecutetheseoperations.Thesedeviceshavecontrollers,andacontrollerinthiscontextmeansamicro-computerwithitsownCPU.ToperformanI/O-boundtask,wedonotneedtowastethemainCPU’stime,itisenoughtogivealltherequireddatatotheI/Odevicecontroller,anditwillperformtheI/Ooperationandreturntheresultswiththehelpofadevicedriver.

TocommunicatewiththeI/Odevices,WindowsusesaspecialobjectcalledI/OCompletionPort(orIOCP).Itbehavesprettymuchlikeatimer,butthesignalsarecomingfromtheI/Odevicesandnotfromtheinternalclock.Thismeansthat,whileanI/Ooperationisinprogress,wecanreusetheASP.NETworkerthreadtoserveotherrequests,andthusachievegoodscalability.Thefollowingdiagramdepictstheprocessesgraphically:

www.EBooksWorld.ir

Page 285: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

NoticeanewentitycalledtheI/Othreadintheprecedingdiagram.ThereisaseparatesmallerpoolofI/Othreadsinsidethis.NETthreadpool.TheI/Othreadsarenotdifferentfromtheusualworkerthreads,buttheyarebeingusedonlytoexecutecontinuationcallbacksforasynchronousI/Ooperations.Ifweusegeneralworkerthreadsforthispurpose,itcanhappenthattherearenoworkerthreadsavailableandwecannotcompletetheI/Ooperation,whichinturnwillleadtodeadlocks.Usingaseparatethreadpoolwillhelptopreventthis,butwealsoneedtobeverycarefulnottocauseI/Othreadsstarvation.Lookatthefollowingexample.

HerewewillcreateanHTTPGETrequestfortheGooglesite.Aswehavealreadylearned,whenweuseawait,allthecodefollowingthelinewithawaitgetswrappedinacontinuationcallbackandiscalledwhentheasynchronousoperationcompletes.HerewewilluseThread.Sleeptoseewhichthreadswillgetbusy:

privatestaticasyncTask<string>IssueHttpRequest()

{

varstr=awaitnewHttpClient().GetStringAsync("http://google.com");

Thread.Sleep(5000);

returnstr;

}

Then,weneedtogetinformationaboutwhatishappeningwiththreadpoolthreads.Fortunately,a.NETthreadpoolhasasetofstaticmethodsthatallowustogetsomeinformationaboutworkerandI/Othreadsinathreadpool:

privatestaticvoidPrintThreadCounts()

{

intioThreads;

intmaxIoThreads;

intworkerThreads;

intmaxWorkerThreads;

ThreadPool.GetMaxThreads(outmaxWorkerThreads,outmaxIoThreads);

ThreadPool.GetAvailableThreads(outworkerThreads,outioThreads);

Console.WriteLine(

"Workerthreads:{0},I/Othreads:{1},Totalthreads:{2}",

maxWorkerThreads-workerThreads,

maxIoThreads-ioThreads,

Process.GetCurrentProcess().Threads.Count

);

}

IntheMainmethod,wewillrunmanyasynchronousI/Otasks;whileiteratingthroughallthesetaskstocompleteineachsecond,wewillprintoutinformationaboutthreadpoolthreads:

privatestaticvoidMain(string[]args)

{

vartasks=newList<Task<string>>();

for(vari=0;i<100;i++)

{

tasks.Add(Task.Run(()=>

www.EBooksWorld.ir

Page 286: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

//Thread.Sleep(5000);

returnIssueHttpRequest();

}));

}

varallComplete=Task.WhenAll(tasks);

while(allComplete.Status!=TaskStatus.RanToCompletion)

{

Thread.Sleep(1000);

PrintThreadCounts();

}

Console.WriteLine(tasks[0].Result.Substring(0,160));

}

Ifwerunthiscode(amongtheothersamplesforChapter8;thisoneiscalledIOThreadsTest),itwillshowthattheI/Othreadnumberwillslowlyincreaseuntilsomepointandgobacktozero.ToprovethattheI/Ooperationreallyhappens,thelastlineswillbethebeginningoftheGooglewebpageHTMLcontent.IfwenowcommentoutthefirstThread.SleepcallanduncommentitintheMainmethod,thesituationwillbedifferent.Wewillblockworkerthreads,andtheI/Othreadnumberwillremainlow.

www.EBooksWorld.ir

Page 287: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 288: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

DeepdiveintoasynchronousI/OUsually,thereisnoneedtouseWin32APItostartanasynchronousI/Ooperation.The.NETbaseclasslibraryhasmanyAPIsthatarecomfortabletouse,andleverageasynchronousI/O.Thefollowingcodeisnotintendedtobeusedinaproductionsoftware,itjustshowshowsuchanAPIcanbewrittenincaseyoudonothaveitinthe.NETFramework.

First,weneedtoallowanunsafecodeinourproject.ThesettingisinsidetheprojectpropertiesoftheBuildsectionasshowninthefollowingscreenshot:

Here,weneedtodefinemanydatastructuresfortheAPIfunctioncalls.ThefullyworkingcodecanbefoundintheBindHandlesampleproject.Inthisbook,wewillskiptheunimportantdetails.

First,weneedtouseP/InvokefortwoWindowsAPIfunctions:

[DllImport("kernel32.dll",SetLastError=true,CharSet=CharSet.Auto)]

publicstaticexternSafeFileHandleCreateFile(

stringlpFileName,

EFileAccessdwDesiredAccess,

EFileSharedwShareMode,

IntPtrlpSecurityAttributes,

ECreationDispositiondwCreationDisposition,

EFileAttributesdwFlagsAndAttributes,

SafeFileHandlehTemplateFile);

[DllImport("kernel32.dll",SetLastError=true)]

unsafeinternalstaticexternintReadFile(

SafeFileHandlehandle,

byte*bytes,

intnumBytesToRead,

www.EBooksWorld.ir

Page 289: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

IntPtrnumBytesRead_mustBeZero,

NativeOverlapped*overlapped);

Then,wecreateafileandwritesometextinitintheusualway:

using(varsw=File.CreateText("test.txt"))

{

sw.WriteLine("Test!");

}

Here,weareopeningthisfileforasynchronousreading.NoticeEFileAttributes.Overlappedinthemethodparameters.IfwewantanasynchronousI/Ooperation,wemustspecifythisflag:

SafeFileHandlehandle=CreateFile(

"test.txt",

EFileAccess.FILE_GENERIC_READ,

EFileShare.Read|EFileShare.Write|EFileShare.Delete,

(IntPtr)null,

ECreationDisposition.OpenExisting,

EFileAttributes.Overlapped,

newSafeFileHandle(IntPtr.Zero,false));

Nowwebindthefilehandletoa.NETthreadpool.ItmaintainsanI/Ocompletionport,andthishandlewillbeattachedtotheport:

if(!ThreadPool.BindHandle(handle))

{

Console.WriteLine("Failedtobindhandletothethreadpool.");

return;

}

Weneedtoprepareabufferforthefilethatisgoingtoberead.Thefollowingcodecheckswhetherthebufferisempty:

byte[]bytes=newbyte[0x8000];

Console.WriteLine("Firstbyteinbuffer:{0}",bytes[0]);

Now,weneedtoprepareacallbackthatwillbeexecutedaftertheasynchronousoperationcompletes.Ifeverythingisfine,wewillgetfilecontentfromthebufferandprintittotheconsole.Wemustcleanuptheresourcesaftertheoperationcompletion:

IOCompletionCallbackiocomplete=delegate(uinterrorCode,uintnumBytes,

NativeOverlapped*nativeOverlapped)

{

try

{

if(errorCode!=0&&numBytes!=0)

{

Console.WriteLine("Error{0}whenreadingfile.",errorCode);

}

Console.WriteLine("Read{0}bytes.",numBytes);

Console.WriteLine(

Encoding.UTF8.GetChars(

newArraySegment<byte>(bytes,0,(int)numBytes).ToArray()));

}

www.EBooksWorld.ir

Page 290: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

finally

{

Overlapped.Unpack(nativeOverlapped);

Overlapped.Free(nativeOverlapped);

}

};

Here,wehavepreparedadatastructuretobepassedtotheasynchronousoperationstart.Wehavetopinourbuffer’saddresstomemory,sothepointerwillbevalid:

Overlappedoverlapped=newOverlapped();

NativeOverlapped*pOverlapped=overlapped.Pack(iocomplete,bytes);

pOverlapped->OffsetLow=0;

fixed(byte*p=bytes)

{

//Herewestartasynchronouslyreadingthefile.

//Whentheoperationwillcomplete,ioComplete

//callbackwillbecalled

intr=ReadFile(handle,p,bytes.Length,IntPtr.Zero,pOverlapped);

if(r==0)

{

r=Marshal.GetLastWin32Error();

if(r!=ERROR_IO_PENDING)

{

Console.WriteLine("Failedtoreadfile.LastErroris{0}",

Marshal.GetLastWin32Error());

Overlapped.Unpack(pOverlapped);

Overlapped.Free(pOverlapped);

return;

}

}

}

Whenwerunthiscode,wewillseethatthefilecontenthasbeensuccessfullyread.

www.EBooksWorld.ir

Page 291: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 292: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

RealandfakeasynchronousI/OoperationsSofar,anasynchronousI/Oseemstobeagoodthingforserverapplications.Unfortunately,thereisquiteunexpectedunderwaterstonethatisveryhardtofind.Let’slookatthefollowingcode.IthappensthattheFileStreaminstancehastheIsAsyncproperty,indicatingthattheunderlyingI/Ooperationisasynchronous.Wewillstartafewasynchronouswritesandcheckwhethertheyarereallyasynchronous:

privateconstintBUFFER_SIZE=4096;

privatestaticasyncTaskProcessAsynchronousIO()

{

using(varstream=newFileStream("test1.txt",FileMode.Create,

FileAccess.ReadWrite,FileShare.None,BUFFER_SIZE))

{

Console.WriteLine("1.UsesI/OThreads:{0}",stream.IsAsync);

varbuffer=Encoding.UTF8.GetBytes(CreateFileContent());

vart=stream.WriteAsync(buffer,0,buffer.Length);

awaitt;

}

using(varstream=newFileStream("test2.txt",FileMode.Create,

FileAccess.ReadWrite,FileShare.None,BUFFER_SIZE,

FileOptions.Asynchronous))

{

Console.WriteLine("2.UsesI/OThreads:{0}",stream.IsAsync);

varbuffer=Encoding.UTF8.GetBytes(CreateFileContent());

vart=stream.WriteAsync(buffer,0,buffer.Length);

awaitt;

}

using(varstream=File.Create("test3.txt",BUFFER_SIZE,

FileOptions.Asynchronous))

using(varsw=newStreamWriter(stream))

{

Console.WriteLine("3.UsesI/OThreads:{0}",stream.IsAsync);

awaitsw.WriteAsync(CreateFileContent());

}

using(varsw=newStreamWriter("test4.txt",append:true))

{

Console.WriteLine("4.UsesI/OThreads:{0}",((FileStream)

sw.BaseStream).IsAsync);

awaitsw.WriteAsync(CreateFileContent());

}

Console.WriteLine("Deletingfiles…");

vardeleteTasks=newTask[4];

for(vari=0;i<4;i++)

www.EBooksWorld.ir

Page 293: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

{

varfileName=string.Format("test{0}.txt",i+1);

deleteTasks[i]=SimulateAsynchronousDelete(fileName);

}

awaitTask.WhenAll(deleteTasks);

Console.WriteLine("Deletingcomplete.");

}

privatestaticstringCreateFileContent()

{

varsb=newStringBuilder();

for(vari=0;i<100000;i++)

{

sb.AppendFormat("{0}",newRandom(i).Next(0,99999));

sb.AppendLine();

}

returnsb.ToString();

}

privatestaticTaskSimulateAsynchronousDelete(stringfileName)

{

//NodeleteasyncinAPI

returnTask.Run(()=>File.Delete(fileName));

}

privatestaticvoidMain(string[]args)

{

vart=ProcessAsynchronousIO();

t.GetAwaiter().GetResult();

}

Whenwerunthecode,wewillseethatonlythenumberstwoandthreewritesareasynchronous.However,wehaveusedtheawaitstatementandcallWriteAsyncinallcases.Whatisgoingon?TheansweristhatifwedonotspecifythecorrectoptionsforthefileAPIweuse,thefilewillprovideuswiththewrongkindofasynchronythatusesworkerthreadsfortheI/Oprocessandthusisnotscalable.

ThisproblemcanbeillustratedbytheSimulateAsynchronousDeletemethod.ThereisnoasynchronousdeletefunctionintheWin32API,soitjuststartsanewtaskwherethesynchronousdeleteisbeingperformed.Thispracticeiscalledasyncoversyncandshouldbeavoided.Donotwriteyourlibrarieslikethis.IfthereisnoasynchronousAPIforsomeoperation,donotmakeitlookasynchronous.Inthefollowingdiagram,wecanseewhyitisabadpracticeforaserverapplication:

www.EBooksWorld.ir

Page 294: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Thisworkflowisevenworsethantheusualsynchronouscode,becausethereisanadditionalperformanceoverheadrelatedtorunningthispartoftheoperationonadifferentworkerthread.WeendupwastingworkerthreadfortheentiretimeoftheI/Ooperationanyway,andthisisfakeasynchronousI/O.ItisactuallyaCPU-boundoperationthatwillaffectthescalabilityandperformanceofyourapplication.

Soifwehavethesourcecodeofalibrary,wecanmakesurethatitleveragesatrulyasynchronousI/O.However,asourcecodeisnotalwaysavailable,andevenifitisavailable,itcanoftenbepuzzlingandcomplicated.Tomakesurethatourasynchronyisright,wecanuseatoolthatshowstheAPIcallsfromtheapplication,andwewillbeabletoseewhetheranI/Ocompletionporthasbeenused.

ThereisaprogramcalledAPIMonitor.Itcanbeeasilyfoundinanysearchengine,isfreetouse,andeasytoinstall.Therearetwoversionsofthisprogram:32-bitand64-bit,soyouhavetopayattentiontowhichversionisappropriateforyourapplication.

Fromthestart,wewillneedtosetupafiltertoseeonlytherequiredfunctioncalls.Foroursamplecode,itisenoughtomonitortwofunctions,CreateFileWandCreateIoCompletionPort.Thefilterisshowninthefollowingscreenshot:

www.EBooksWorld.ir

Page 295: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ThenweneedtorunourapplicationunderAPIMonitor.Tostartmonitoring,pressCtrl+MorusetheFile|MonitorNewProcess…menuoption.Thestartdialogwillappearasfollows:

www.EBooksWorld.ir

Page 296: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

WhenyoupressOK,theapplicationwillstartandthenyouwillseeareportasfollows:

Youcanseethattowritethetest2.txtfile,theFILE_FLAG_OVERLAPPEDflagwasprovidedtotheCreateFileWfunction,meaningthatweareusingtheI/Ocompletionport.TheCreateFileWfunctionreturnedthe0x234filehandle,whichwasboundtotheI/OcompletionportbycallingtheCreateIoCompletionPortfunction.Thefirstandthelastfilewritesarenotusingthecompletionportandthusarenotreallyasynchronous.

www.EBooksWorld.ir

Page 297: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 298: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SynchronizationcontextAnotherveryimportantconceptissynchronizationcontext.Wewillreviewsynchronizationcontextandotherkindsofcontextindetailinthenextchapter,butfornowlet’sstartwithademonstration.ThissampleiscalledIISSynchronizationContext.ThistimeweneedtohostourapplicationinanIISwebserver,sowewillusetheMicrosoft.Owin.Host.SystemWebNuGetpackage,andcreateanemptyASP.NETapplication.First,wewillconfigureourapplicationanddefineadefaultroute:

publicclassStartup

{

publicvoidConfiguration(IAppBuilderappBuilder)

{

varconfig=newHttpConfiguration();

config.Routes.MapHttpRoute(

"DefaultApi","api/{controller}/{action}/{id}",new{id=

RouteParameter.Optional}

);

appBuilder.UseWebApi(config);

}

}

Thenwewillcreateacontrollerwithtwomethods.Oneofthemtriestogetasynchronousoperationresultssynchronously,andtheotherusesawaitandasynchronousexecution:

publicclassHomeController:ApiController

{

[HttpGet]

publicintSync()

{

varlib=newAsyncLib();

returnlib.CountCharactersAsync(newUri("http://google.com")).Result;

}

[HttpGet]

publicasyncTask<int>Async()

{

varlib=newAsyncLib();

returnawaitlib.CountCharactersAsync(newUri("http://google.com"));

}

}

HerewehavedefinedourasynchronousoperationasdownloadingcontentfromagivenURLandreturningitslengthincharacters:

publicclassAsyncLib

{

publicasyncTask<int>CountCharactersAsync(Uriuri)

{

using(varclient=newHttpClient())

{

www.EBooksWorld.ir

Page 299: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

varcontent=awaitclient.GetStringAsync(uri)

//.ConfigureAwait(continueOnCapturedContext:false);

returncontent.Length;

}

}

}

WhenwerunthiscodeinVisualStudio,adefaultwebbrowsershouldstartandopenthewebapplicationURL.Inthesamplecode,bothactionscanbereachedviahttp://localhost:5098/api/Home/Asyncandhttp://localhost:5098/api/Home/Syncrespectively.TheAsyncversionworksfine,whiletheSynccodehangsforever.

ThiscanbefixedifweuncommenttheConfigureAwaitlineintheAsyncLibclass.Ifyourunanewcode,theSyncversionwillalsowork.Tounderstandthereasonsforthis,weneedtogetbacktothesynchronizationcontextconcept.Asynchronizationcontextrepresentsanenvironmentthathassomedataassociatedtoit,andanabilitytorunadelegateusingthisenvironment.InASP.NET,whenusingaIISwebserverthereisaspecialsynchronizationcontextthatkeepsthecurrentcultureanduseridentity.

Nowwhenweuseawaitbydefault,ifweuseawaitwiththeTask<T>instance,wewillgetaspecialTaskAwaiter<T>structurethatisusedbytheC#compilerinthegeneratedstatemachinecode.Torunacontinuationcallback,C#endsupcallingtheUnsafeOnCompletedmethod:

publicstructTaskAwaiter<TResult>:ICriticalNotifyCompletion

{

privatereadonlyTask<TResult>m_task;

internalTaskAwaiter(Task<TResult>task)

{

Contract.Requires(task!=null,"Constructinganawaiterrequiresa

tasktoawait.");

m_task=task;

}

publicvoidUnsafeOnCompleted(Actioncontinuation)

{

TaskAwaiter.OnCompletedInternal(m_task,continuation,

continueOnCapturedContext:true,flowExecutionContext:false);

}

}

Sothiscodetriestopostacontinuationcallbacktothecurrentsynchronizationcontext.Howeverwhenwerunthiscodesynchronously,wewillgetaclassicdeadlocksituation:

ThecodeblocksthecurrentsynchronizationcontextuntiltheoperationcompletesTheoperationcompletesandpoststhecontinuationcallbacktothecurrentsynchronizationcontextHowever,itisblockeduntilwegetaresultandcannotrunthiscontinuationcallback,andthuscannotgetaresultAllthisleadstoadeadlock

Topreventthis,wecanusetheConfigureAwait(continueOnCapturedContext:false)

www.EBooksWorld.ir

Page 300: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

method.ItreturnsaspecialConfiguredTaskAwaitabletypeinstance,whichinturnreturnsConfiguredTaskAwaitertotheC#compiler-generatedcode.Inthiscase,weuseUnsafeOnCompletedaswell,butthistimeitisspecificallyconfigurednottocapturethecurrentsynchronizationcontext,andthecontinuationcallbackgetspostedtoadefaulttaskscheduler,whichislikelytobeathreadpoolworkerthread:

publicstructConfiguredTaskAwaiter:ICriticalNotifyCompletion

{

privatereadonlyTask<TResult>m_task;

privatereadonlyboolm_continueOnCapturedContext;

internalConfiguredTaskAwaiter(Task<TResult>task,bool

continueOnCapturedContext)

{

Contract.Requires(task!=null,"Constructinganawaiterrequiresa

tasktoawait.");

m_task=task;

m_continueOnCapturedContext=continueOnCapturedContext;

}

publicvoidUnsafeOnCompleted(Actioncontinuation)

{

OnCompletedInternal(m_task,continuation,m_continueOnCapturedContext,

flowExecutionContext:false);

}

}

Thismeansthatwhenyouwritealibrarywithasyncmethodsthathavetheawaitstatementsinsideandifyouaresurethatyourcontinuationcodedoesnotneedthecurrentsynchronizationcontext,alwaysuse.ConfigureAwait(false)topreventsuchsituations.Alsoviceversa;ifyouhavetoworkwithasynchronousoperationssynchronouslyinASP.NET,itisverydangeroustousetheTask.Resultpropertyandblockthecurrentthread.YoushoulduseTask.ContinueWithalongwiththecorrespondingoptionstogettheresultwithoutawait.

www.EBooksWorld.ir

Page 301: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 302: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CPU-boundtasksandqueuesSofar,wehavereviewedmanyspecialdetailsaboutI/O-boundtasks,butwhataboutCPU-boundwork?Technically,themostefficientwaywillbetorunsuchworksynchronouslyandscalehorizontallybyaddingmoreandmoreserverstobeabletohandleincreasingload.Nevertheless,itcanhappenthatthisCPU-boundworkisnottheonlyresponsibilityofaserverapplication.Inthiscase,wecanfindawaytogetthistoworkoutofthewebapplication,allowingittorunfast;nowtheCPU-boundpartcanbescaledseparatelyanddoesnotaffecttherestofthisapplication.

Thisishowcloudapplicationswork.Usually,ifthereisalongrunningoperation,awebapplicationregistersitintosomedatastore,returnsauniqueidentifierofthisoperationtotheclient,andpoststhisoperationtoaspecialqueue.Thenthereisaseparatepoolofworkerprocessesthatmonitorthisqueue,gettasksfromthem,processthem,andwriteresultstoadatastore.Whentheclientarrivesnexttime,thewebapplicationcheckswhetherthetaskhasbeenalreadycompletedbyanyworkerandifithas,theapplicationreturnstheresulttotheclient.

www.EBooksWorld.ir

Page 303: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 304: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,wehavelearnedaboutserverapplicationsandhowtheyaredifferent.Wehavelookedatscalabilityandunderstoodwhyitisveryimportantforaserverapplicationtobeabletoscalewell.WehavecreatedanOWINWebAPIapplicationandlearnedtohostitinanIISwebserverandinaseparateprocess.WehaveusedVisualStudiotocreateloadtestsforourserverapplication,checkedwhathappenswhenweusegoodandpoorlywrittenasynchronouscode,andleveragedtheApachebenchcommandlinetooltorunbenchmarkswithoutVisualStudio.

WealsohavereviewedindetailwhatanI/OthreadandanI/Ocompletionportare,andfoundoutreasonswhyusinganasynchronousI/Oisthekeytobuildingscalableserverapplications.Tocheckwhetherathird-partycodeusesrealasynchronousI/O,wehavefoundatoolthatshowsWin32APIcalls.Inconclusion,wehavelearnedaboutsynchronizationcontextandhowwecanconfigurecontinuationtaskstoberunonadefaulttaskscheduler.Finally,wehavediscussedhowtoenhancethescalabilityofaserverapplicationthathaslong-runningCPU-boundtasks.

Inthenextchapter,wewillreviewclientapplications,andspecificallytheuserinterfacepart,indetail.Wewilllearnaboutmodernuserinterfacetechnologies,howtokeeptheUIfastandresponsive,andhowtoavoidcommonpitfallsandmistakeswithasynchronyontheclientside.

www.EBooksWorld.ir

Page 305: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 306: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter9.ConcurrencyintheUserInterfaceInthischapter,wewillreviewtheaspectsofusingasynchronyinclientapplications.WewilllearnabouthowaWindowsapplicationworksanddefinewhatanUIthreadandmessageloopis.Whilegoingthroughthedetailsofexecutionandsynchronizationcontexts,wewilldigintoaC#compiler-generatedcodeandseehowitisrelatedtotheuseoftheawaitkeywordinyourprogram.

www.EBooksWorld.ir

Page 307: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TheimportanceofasynchronyforUIWhileaserverapplicationingeneralhastobescalablebeforeeverythingelse,aclientapplicationisdifferent.Itisusuallyintendedtorunforoneuserononecomputer,andthustheuserexpectsittorunfastandnotcausetroublesfortheotherapplicationsrunninginthesystem.Whilethesecondpartisusuallyhandledbytheoperatingsystem,theapplication’sperformanceingettingthingsdoneandreactingtouserinputiscrucialforapositiveuserexperience.Imagineifyourunanapplicationandithangsforafewminutesafteryouclickonabutton.Agoodapplicationremainsresponsiveandindicatesthatyouhavejuststartedalong-runningoperationthatisstillrunningandisgoingtocompletesoon.Meanwhile,youcandosomethingelse—clickonotherbuttonsandperformsomeothertasks.Whenthetaskiscompleted,youcangetbacktoitandseetheresult.

However,achievingjustthisisoftennotenough.Ifyouusesomeapplicationanditreactstoyourinputevenwithaslightdelay,itwillbestillveryannoying.Itishumannaturetoexpectanimmediatereaction,andevensmalldelayscancauseirritationandanger.ThisrequiresaprogramtooffloadworkfromtheUIasmuchaspossible,andforthiswehavetolearnhowtheUIworksandtheUIthreadingarchitecture.Laterinthischapter,wewillgodeeperintothedetails.

Thelastaspectisnotrelevanttothischapter,butisstillveryimportant.Whileaserverapplicationhastoconsumeasfewresourcesperuseraspossible,ifyourprogramneedscomputationalpowerthenithastobeabletousethenecessaryresources.Forinstance,ifauser’scomputerhasfourcoreCPUswithhyperthreadingtechnology,thentheapplicationhastobeabletouseallthelogicalcorestogettheresultassoonaspossible.Thisiswherethisbook’scontentwillbeveryuseful,especiallyChapter7,LeveragingParallelPatterns,whichprovidesyouwithconcurrentprogrammingpatterns.

www.EBooksWorld.ir

Page 308: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 309: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UIthreadsandmessageloopsModernUIframeworkandprogramminglanguagesnotonlymakeclientapplicationdevelopmentmucheasierthanbefore,buttheyalsoraisealevelofabstractionandhideimportantimplementationdetails.TounderstandhowtheUIworks,weshouldlookatthelower-levelcode.

Thefollowingisthecodeofasimplewin32program,whichiswritteninC.IfyourVisualStudiodoesnothaveC/C++projectsupportinstalled,itisnotaproblem.ThiscodeisneededjusttoillustratehowaWindowsapplicationworks,andwe’llbreakitintopartsandexamineeachpartindetail.First,let’slookatthefullprogramcodelisting:

#include<windows.h>

constchar_szClassName[]="ConcurrencyInUIWindowClass";

LRESULTCALLBACKWndProc(HWNDhwnd,UINTmsg,WPARAMwParam,LPARAMlParam)

{

switch(msg)

{

caseWM_CLOSE:

DestroyWindow(hwnd);

break;

caseWM_DESTROY:

PostQuitMessage(0);

break;

default:

returnDefWindowProc(hwnd,msg,wParam,lParam);

}

return0;

}

intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTR

lpCmdLine,intnCmdShow)

{

WNDCLASSEXwc;

HWNDhwnd;

MSGmsg;

//CreatingtheWindowclass

wc.cbSize=sizeof(WNDCLASSEX);//sizeoftheinstance

wc.style=0;//classstyles,notimportanthere

wc.lpfnWndProc=WndProc;//thepointertoWindowprocedure

wc.cbClsExtra=0;//extradataforthisclass

wc.cbWndExtra=0;//extradataforthisWindow

wc.hInstance=hInstance;//applicationinstancehandle

wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);//standardlarge//icon

wc.hCursor=LoadCursor(NULL,IDC_ARROW);//standardarrow//cursor

wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);//background//brush

wc.lpszMenuName=NULL;//nameofmenuresource

wc.lpszClassName=_szClassName;//Windowclassname

wc.hIconSm=LoadIcon(NULL,IDI_APPLICATION);//standardsmall//icon

www.EBooksWorld.ir

Page 310: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

if(!RegisterClassEx(&wc))

{

MessageBox(NULL,"Windowclassregistrationfailed!",

"Error!",MB_ICONEXCLAMATION|MB_OK);

return0;

}

hwnd=CreateWindowEx(

WS_EX_CLIENTEDGE,

_szClassName,

"UIConcurrency",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,CW_USEDEFAULT,480,240,

NULL,NULL,hInstance,NULL);

if(hwnd==NULL)

{

MessageBox(NULL,"Windowcreationfailed!",

"Error!",MB_ICONEXCLAMATION|MB_OK);

return0;

}

ShowWindow(hwnd,nCmdShow);

UpdateWindow(hwnd);

while(GetMessage(&msg,NULL,0,0)>0)

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

returnmsg.wParam;

}

TheentrypointistheWinMainmethod,whichisageneralentrancepointforallWindowsapplications.Thisiswhatwillbecalledwhentheapplicationstarts.Thismethodisquitebig,butbasicallyitconsistsoffourmainsteps.

ThefirststepistocreatetheWindowclassinstance,andprovideitwiththedatarequired.ThemostimportantparthereisthepointertotheWindowprocedure.Inourcase,itistheWndProcmethod,anditwillbeusedlatertoprocessmessagesfromtheoperatingsystem.Also,weneedauniquestringnameforourWindowclasstouseittocreateawindowinourapplication.

ThesecondstepbeginswheretheRegisterClassExmethodiscalled.WeregistertheWindowclassandimmediatelyuseitsnametocreatethemainapplicationwindowusingtheCreateWindowExfunctioncall.Thiscallreturnsahandlethatisneededforalmosteveryoperationrelatedtothiswindow.ThenwedisplaytheapplicationwindowonthescreenusingtheShowWindowandUpdateWindowmethods.

Thethirdstepisveryimportantandevenhighlightedinthecodelisting.Thisiswhatisusuallycalledthemessagelooporthemessagepump.ThiscyclecallstheGetMessagemethodthatgetsthefirstmessagefromthemessagequeue.Thisqueueiscreatedwhena

www.EBooksWorld.ir

Page 311: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

threadcreatesatleastonewindowandthusbecomestheUIthread.Ifthemessagequeueisempty,theGetMessagemethodcallgetsblockeduntilanymessagesappearanditdequeuesthefirstmessage.Theoperatingsystemputsmessagessuchasakeypressoramouseclickonthisqueue,andthenthismessagegetssomepreprocessingbytheTranslateMessagefunction.ThenDispatchMessagesendsthismessagetotheWindowprocedurethatisappointedtotheWindowclassthatwehaveusedtocreatethemainapplicationwindow.InourcaseitistheWndProcmethod,anditisresponsibleforreactingtotheoperatingsystemandtheapplicationevents.WhentheGetMessagemethodreturnsaresultthatislessthanzero,themessageloopstopsandtheapplicationexits.

Sothefinalstep,thatisstepfour,isthemessageprocessinginsideWndProc.Thishasfourparameters:hwndistheWindowhandleandallowsyoutointeractwiththewindow,msgisthemessageid,andwParamandlParamcontainspecificdataforeachsystemmessage.InourWindowprocedure,wehandletheWM_CLOSEandWM_DESTROYmessagesexplicitlytoshowanexampleofmessagehandling,andbydefault,wepassallmessagestoastandardmessagehandler.Ifweruntheapplication,wewillseethatitshowstheemptyapplicationwindowwiththecustomtitle.

Nowlet’saddthecodetoshowasimplebuttonclickhandlerthatwillstartanasynchronousoperation.ThiscodereplacestheWndProcdefinitionfromtheprecedingcodelisting:

constUINTIDC_START_BUTTON=101;

constUINTWM_ASYNC_TASK_COMPLETED=WM_USER+0;

DWORDWINAPISimulateAsyncOperation(LPVOIDlpHwnd)

{

//pretendingthatthisisanasyncoperation

//poststhemessagetotheUImessageloop

//fromotherthread

HWNDhwnd=*((HWND*)lpHwnd);

Sleep(10000);

SendMessage(hwnd,WM_ASYNC_TASK_COMPLETED,NULL,NULL);

return0;

}

LRESULTCALLBACKWndProc(HWNDhwnd,UINTmsg,WPARAMwParam,LPARAMlParam)

{

switch(msg)

{

caseWM_CREATE:

{

HGDIOBJhfDefault=GetStockObject(DEFAULT_GUI_FONT);

HWNDhWndButton=CreateWindowEx(NULL,

"BUTTON",

"OK",

WS_TABSTOP|WS_VISIBLE|

WS_CHILD|BS_DEFPUSHBUTTON,

50,

80,

100,

24,

hwnd,

www.EBooksWorld.ir

Page 312: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

(HMENU)IDC_START_BUTTON,

GetModuleHandle(NULL),

NULL);

SendMessage(hWndButton,

WM_SETFONT,

(WPARAM)hfDefault,

MAKELPARAM(FALSE,0));

}

break;

caseWM_COMMAND:

switch(LOWORD(wParam))

{

caseIDC_START_BUTTON:

{

HANDLEthreadHandle=CreateThread(NULL,0,

SimulateAsyncOperation,

&hwnd,0,NULL);

//wedonotneedthehandle,sojustcloseit

CloseHandle(threadHandle);

MessageBox(hwnd,

"Startbuttonpressed",

"Information",

MB_ICONINFORMATION);

}

break;

}

break;

caseWM_ASYNC_TASK_COMPLETED:

MessageBox(hwnd,

"Operationcompleted",

"Information",

MB_ICONINFORMATION);

break;

caseWM_CLOSE:

//sendsWM_DESTROY

DestroyWindow(hwnd);

break;

caseWM_DESTROY:

//Windowcleanuphere

PostQuitMessage(0);

break;

default:

returnDefWindowProc(hwnd,msg,wParam,lParam);

}

return0;

}

Inthebeginning,wehavecreatedidentifiersforabuttonandacustommessage.Thedetailsarenotrelevanthere;theyarejustsomenumericidentifiers.ThenextpartisanasynchronousoperationcodeinsidetheSimulateAsyncOperationmethod.Itjustblocksthecurrentthreadfor5secondsandthensendsacustommessagetotheWindowhandlethatitgetsthroughtheinputparameter.

TheremainingcodeisplacedinsidemessagehandlersintheWndProcWindowprocedure.www.EBooksWorld.ir

Page 313: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ThefirstimportantplaceistheWM_CREATEmessagehandler.Herewecreatedabutton,andsetthebuttontextfonttoadefaultsystemfont.Theotherdetailsarenotimportanthere;justnoticetheuseofIDC_START_BUTTONinsidetheCreateWindowExmethodcall.Thisidentifierwillbeusedlaterinthemessagethattheoperatingsystemwillsendwhenthisbuttonisclicked.ThismessagewillbeprocessedbytheWM_COMMANDmessagehandler.Thesendingelementidentifierispassedinalow-orderwordofthewParamvalue.Inthecaseofourbuttonclick,thisvaluewillbeIDC_START_BUTTON.WecanthinkofthislikethecommonButton_Clickhandlerinhigher-levelframeworkssuchasWindowsFormsorWPF.Insidethisbuttonclickhandler,wehavecreatedaseparatethreadthatwillruntheSimulateAsyncOperationmethod.Thenthesimplestsolutionistoshowamodalmessageboxshowingthattheoperationhasbeenstarted.

Thelast,butnottheleast,stepishowweruncontinuationcodeaftertheasynchronousoperationcompletes.Theoperationsendsacustommessage,andwehandleitwiththeWM_ASYNC_TASK_COMPLETEDmessagehandler.Itsimplyshowsamessageboxinformingaboutthattheoperationhasbeencompleted.Theoperationtakes10secondstocomplete,soyoucanclosethefirstmessageboxanddragaroundtheapplicationwindowtomakesurethatitstaysresponsive.

Ofcourse,ifwerunSimulateAsyncOperationontheUIthread,itwillfreeze.Simplyreplacethebuttonclickhandlercodewiththistomakesurethisreallyhappens:

caseIDC_START_BUTTON:

{

MessageBox(hwnd,

"Startbuttonpressed",

"Information",

MB_ICONINFORMATION);

SimulateAsyncOperation(&hwnd);

}

break;

Nowifwerunthecodewiththesechanges,theapplicationwindowwillstoprespondingfor10secondsafterwepressthebuttonandclosethemodaldialog.Thisperfectlyillustrateswhatwearetryingtoachieve;doalltheworkontheotherthreads,leavetheUIthreadjusttohandlemessagesasfastaspossible,andyouwillgetagreatandresponsiveUIinyourapplication.

However,inmodernUIprogramming,theabstractionlevelisveryhigh,andusuallyyoucannotbesureifsomecoderunsontheUIthreadornotjustbylookingatit.ConsiderthisC#codethatcanbeapartofanyWPFapplication:

privatestaticasyncvoidClick(objectsender,EventArgse)

{

MessageBox.Show("Startingasynchronousoperation….");

awaitSomeOperationAsync();

MessageBox.Show("Asynchronousoperationcomplete!");

}

Thisisabuttonclickhandlerthatlogicallydoesthesamethingasthepreviouscode—www.EBooksWorld.ir

Page 314: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

showsadialog,runsanasynchronousoperation,andnotifiesuswithmessageboxesaboutthestartandendoftheoperation.ItismuchsimplerthanthenativeWin32applicationwindowproceduremessagehandlingcode.However,wepaythepricebynotknowingthedetails,andbyjustlookingatthispieceofcode,wecannotsayanythingaboutwhatthreadwillrunwhichpartofthiscode.

www.EBooksWorld.ir

Page 315: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 316: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CommonproblemsandsolutionsToseewhatcanhappenifwedonotcontrolhowexactlythecodecorrelatestothreads,let’sstartwithasimpleWPFapplicationthathasthreedifferentbuttons.Inthisparticularcase,itisnotrelevanthowtheWPFapplicationgetscreatedandhowwecomposeUIcontrols,sowearegoingtoconcentrateonthecodeinsidethebuttonclickhandlers.AllthecodeforthissampleislocatedintheAsyncInUIprojectinthesamplesforChapter9.Besidesthis,wewillnotuseasyncandawaityet,becausetheywillcreateonemoreabstractionlevelandthusmakethecodehardertounderstand.

ThefirstbuttontriestocallaTaskreturningmethodsynchronously:

privatestaticvoidSyncClick(objectsender,EventArgse)

{

_label.Content=string.Empty;

try

{

stringresult=TaskMethod().Result;

_label.Content=result;

}

catch(Exceptionex)

{

_label.Content=ex.Message;

}

}

WithoutknowingexactlywhatTaskMethodis,itisimpossibletopredicthowthisprogramwillbehave.Fornow,wewillexperimentandonlythenlookatitscodeandseewhathappened.IfweruntheapplicationandclickontheStartsynchronousoperationbutton,besidesanunresponsiveUI,wewillgetaweirderrormessage:

Oneormoreerrorsoccurred

FromChapter5,C#LanguageSupportforAsynchrony,wealreadyknowthatthisisamessagefromtheAggregateExceptioninstance.TheeasiestwaytogettherealexceptionmessageisbygettingtheTaskresultthroughtheGetAwaitermethodcall.Thenewlineofcodewillbe:

stringresult=TaskMethod().GetAwaiter().GetResult();

ThistimetheUIgetsblockedagain,butwegettheactualerrormessage:

Thecallingthreadcannotaccessthisobjectbecauseadifferentthread

ownsit.

ThismessagetellsusthatwearetryingtoaccessaUIcontrolfromanon-UIthread,whichisnotallowed.NowisthetimetodigintotheTaskMethodcode:

privatestaticTask<string>TaskMethod()

{

returnTaskMethod(TaskScheduler.Default);

}

www.EBooksWorld.ir

Page 317: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

privatestaticTask<string>TaskMethod(TaskSchedulerscheduler)

{

Taskdelay=Task.Delay(TimeSpan.FromSeconds(5));

returndelay.ContinueWith(t=>

{

stringstr=string.Format(

"Taskisrunningonathreadid{0}.Isthreadpoolthread:{1}",

Thread.CurrentThread.ManagedThreadId,

Thread.CurrentThread.IsThreadPoolThread);

_label.Content=str;

returnstr;

},

scheduler);

}

Sowecanseethatwehavecreatedatimertaskandthensetupacontinuationtaskusingthedefaulttaskscheduler,whichtriestosetthelabeltext.Sincethedefaulttaskschedulerpoststhetaskcodetothethreadpool,wegetamultithreadedaccesserror.

Wehavealreadycoveredtaskschedulersearlierinthebook,andweknowthatwecangetoneforthecurrentsynchronizationcontext.Fornow,let’ssaythatthiswillallowustopostthecodetotheUIthread,andthiswouldresolvetheissuethatwehave.Itseemsthatifwemodifythecodetouseapropertaskscheduler,wewillgettherequiredresult:

stringresult=TaskMethod(

TaskScheduler.FromCurrentSynchronizationContext()).Result;

Unfortunately,ifwerunthemodifiedprogramandpressthebutton,theapplicationwillhang.ThereasonforthiswillbecomeclearwhenwegetbacktotheWndProcWindowproceduresourcecode.WewillmakeablockingcalltoTaskMethodfromthebuttonclickhandler,waitingfortheasynchronousoperationtocomplete.However,thebuttonclickhandlerrunsontheUIthread,sothisstopsthemessageloopfromspinningandtherefore,wewillnevergetamessagefromtheasynchronousoperationbecausethemessageloopcannotprocessthemessageasitisstopped.ItisaclassicdeadlocksituationandshowsthatusingsynchronouscallsontasksontheUIthreadisquitedangerous.

Nevertheless,wecanmakethiscodework.WPFallowsustorunthemessageloopmanually:

publicstaticclassTaskExtensions

{

publicstaticTWaitWithNestedMessageLoop<T>(thisTask<T>task)

{

varnested=newDispatcherFrame();

task.ContinueWith(_=>nested.Continue=false,TaskScheduler.Default);

Dispatcher.PushFrame(nested);

returntask.Result;

}

}

Thiscodecreatesanestedmessageloop.Thismeansthatthemainmessagelooppauses,thisonestartstoprocessmessagesuntilwestopit,andthenthemainloopgetsbackin

www.EBooksWorld.ir

Page 318: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

control.Sofirst,wecreatedthenestedmessageloop.Thenwesetupacontinuationtaskthatisgoingtorunonathreadpoolworkerthread.Thistaskwillstopthenestedmessageloopwhentheinitialtaskcompletes.

Finally,westartedthenestedmessageloop.ThePushFramemethodcallisblockeduntilsomeonesetstheContinuepropertyonthemessagelooptofalse.ThenestedmessageloopwillprocesssystemeventsandallowtheUItostayresponsivewhilewewaitfortheinitialtasktocomplete.Whenthiscompletes,thecontinuationtaskstopsthenestedmessageloopbysettingitsContinuepropertytofalse,andthenwewillgetthetaskresult(whichwillnotblocknow,becausethetaskhasbeencompleted)andreturnit.

Now,let’schangethecodeandrunit:

stringresult=TaskMethod(

TaskScheduler.FromCurrentSynchronizationContext())

.WaitWithNestedMessageLoop();

TheUIstaysresponsive,andwegetamessageaboutthecodethatworkswhilealabelcontrolrunsontheUIthread,whichisexactlywhatwewantedtoachieve.

Anasynchronouscode,however,willworkfine,becauseitdoesnotblocktheUIthreadandthemessageloop.Toprovethis,let’strytorunasynchronousoperationsonthethreadpoolandontheUIthread:

privatestaticvoidAsyncClick(objectsender,EventArgse)

{

_label.Content=string.Empty;

Mouse.OverrideCursor=Cursors.Wait;

Task<string>task=TaskMethod();

task.ContinueWith(t=>

{

_label.Content=t.Exception.InnerException.Message;

Mouse.OverrideCursor=null;

},

CancellationToken.None,

TaskContinuationOptions.OnlyOnFaulted,

TaskScheduler.FromCurrentSynchronizationContext()

);

}

privatestaticvoidAsyncOkClick(objectsender,EventArgse)

{

_label.Content=string.Empty;

Mouse.OverrideCursor=Cursors.Wait;

Task<string>task=TaskMethod(

TaskScheduler.FromCurrentSynchronizationContext());

task.ContinueWith(t=>Mouse.OverrideCursor=null,

CancellationToken.None,

TaskContinuationOptions.None,

TaskScheduler.FromCurrentSynchronizationContext());

}

Sincewedidnotwanttouseawaithere,wehavetosetupcontinuationtaskstooutput

www.EBooksWorld.ir

Page 319: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

theresult.IntheAsyncClickmethod,weknowthattheasynchronouscallisgoingtofail,sowesetupanerrorhandlingcontinuationtaskusingtheUIthreadtaskscheduler.Inthesecondcase,everythingisgoingtobefine,sothecontinuationtaskwillshowasuccessmessage.Runningtheprogramandclickingonthesecondandthirdbuttonsprovesourassumptions.

www.EBooksWorld.ir

Page 320: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 321: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

HowtheawaitkeywordworksNowlet’swriteabuttonclickhandlerusingawaitandseewhathaschanged:

privatestaticasyncvoidClick(objectsender,EventArgse)

{

_label.Content="Startingasynchronousoperation….";

awaitSomeOperationAsync();

_label.Content="Asynchronousoperationcomplete!";

}

Onceagain,withoutknowingexactlywhatSomeOperationAsyncis,itisstillimpossibletoknowhowthiscodeisgoingtobehave.Imaginethesimplestasynchronousmethodimplementation:

staticTaskSomeOperationAsync()

{

returnTask.Delay(TimeSpan.FromSeconds(5));

}

Inthiscase,theprogramwillrunsuccessfully,whichmeansthatthecontinuationcoderunsontheUIthread.Tofindouthowthishappens,weneedtoreviewtwoimportantabstractions:executionandsynchronizationcontexts.

www.EBooksWorld.ir

Page 322: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ExecutionandsynchronizationcontextsAnexecutioncontextcontainsallthedatarelatedtothecurrentenvironmentinwhichathreadisrunning.Usually,thereisnoneedtousethisdirectly;itisusedbytheframeworktocontainthethread’slocalinformationsuchassecurityinformation.Whenneeded,itispossibletorestorethisinformationtoanotherthread.theC#infrastructurecapturestheexecutioncontextandflowsitintoacontinuationcodebydefault.

HereisanexampleofthecodegeneratedbytheC#compilertoperformanasynchronousmethodcallwithawait:

publicAsyncVoidMethodBuilder<>t__builder;

...

TaskAwaiterawaiter=Program.SomeOperationAsync().GetAwaiter();

...

//incasetheoperationisnotcompletedyet

this.<>__builder.AwaitUnsafeOnCompleted(refawaiter,refthis);

SoifwelookattheAsyncVoidMethodBuilder.AwaitUnsafeOnCompletedmethod,itwillcontainthefollowingcode:

[SecuritySafeCritical]

publicvoidAwaitUnsafeOnCompleted<TAwaiter,TStateMachine>(

refTAwaiterawaiter,refTStateMachinestateMachine)

whereTAwaiter:ICriticalNotifyCompletion

whereTStateMachine:IAsyncStateMachine

{

try

{

varcontinuation=m_coreState

.GetCompletionAction(refthis,refstateMachine);

Contract.Assert(continuation!=null,

"GetCompletionActionshouldalwaysreturnavalidaction.");

awaiter.UnsafeOnCompleted(continuation);

}

catch(Exceptione)

{

AsyncMethodBuilderCore.ThrowAsync(e,targetContext:null);

}

}

Now,wegetacontinuationdelegatebycallingtheGetCompletionActionmethod:

internalActionGetCompletionAction<TMethodBuilder,TStateMachine>(

refTMethodBuilderbuilder,refTStateMachinestateMachine)

whereTMethodBuilder:IAsyncMethodBuilder

whereTStateMachine:IAsyncStateMachine

{

...

//ThebuilderneedstoflowExecutionContext,socaptureit.

varcapturedContext=ExecutionContext.FastCapture();

www.EBooksWorld.ir

Page 323: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

...

}

So,wecapturethecurrentsynchronizationcontextanduseittorunacontinuationcode.

Synchronizationcontextisanotherconceptthatabstractsawaytheimplementationdetailsofsomeenvironmentthatisabletorunthecode.ItcanbeaWindowsFormsenvironmentthatrunsadelegatewiththehelpoftheControl.BeginInvokemethod,aWPFenvironmentthatcanrunthecodeusingtheDispatcherobject,orjustanyotherframeworkthatneedssuchanenvironmenttorunthecode.

Let’slookattheprecedingcode,specificallyattheawaiter.UnsafeOnCompleted(continuation)part.TheC#asyncinfrastructureusestheTaskAwaitertypefortheawaitervariable,whichhasthefollowingUnsafeOnCompletedmethod:

[SecurityCritical]

publicvoidUnsafeOnCompleted(Actioncontinuation)

{

TaskAwaiter.OnCompletedInternal(m_task,

continuation,

continueOnCapturedContext:true,

flowExecutionContext:false);

}

Youcanseethatwecapturedthecurrentsynchronizationcontext.However,noticethattheflowExecutionContextparameterissettofalse.Thisonlymeansthattheexecutioncontextflowhappensinanotherplaceinthecode;hereweareonlycapturingthecurrentsynchronizationcontext.

Well,nowweunderstandhowtheC#asynchronousinfrastructuremakesthecurrentexecutionandsynchronizationcontextsrunacontinuationcode.Isitpossibletochangethisbehavior?Theanswerisyes,itispossible.Tostopcapturingthecurrentsynchronizationcontext,wecanusethespecialConfigureAwaitmethodontheTaskinstance:

privatestaticasyncvoidClick(objectsender,EventArgse)

{

_label.Content="Startingasynchronousoperation….";

awaitSomeOperationAsync()

.ConfigureAwait(continueOnCapturedContext:false);

_label.Content="Asynchronousoperationcomplete!";

}

UsingtheConfigureAwaitmethodwillleadtoanotherawaitertype,ConfiguredTaskAwaiter.ThiswillbeusedbytheC#asynchronousinfrastructure.ItimplementsUnsafeOnCompletedslightlydifferently:

[SecurityCritical]

publicvoidUnsafeOnCompleted(Actioncontinuation)

{

TaskAwaiter.OnCompletedInternal(m_task,

continuation,

www.EBooksWorld.ir

Page 324: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

m_continueOnCapturedContext,

flowExecutionContext:false);

}

WecanseethatprovidingfalsetotheConfigureAwaitmethodwillcausethesynchronizationcontexttonotbecaptured.Ifwerunthemodifiedapplicationandpressthebutton,wewillgetamultithreadedUIcontrolaccessexception.

www.EBooksWorld.ir

Page 325: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 326: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

PerformanceissuesSofar,wehaveonlyobservedproblemsrelatedtomultithreadedaccesstotheUIcontrols.Bydefault,theC#awaitstatementwillusethecurrentsynchronizationandexecutioncontextsandpostthecontinuationcodetotheappropriateenvironment.IsthereanyusefortheConfigureAwaitmethod?Whyshouldweevertrytochangethedefaultbehavior?Toanswerthisquestion,considerthefollowingapplication.Thistimewewillreviewthewholecodeincludingtheonethatassemblestheapplication:

privatestaticLabel_label;

[STAThread]

staticvoidMain(string[]args)

{

varapp=newApplication();

varwin=newWindow();

varpanel=newStackPanel();

varbutton=newButton();

_label=newLabel();

_label.FontSize=32;

_label.Height=200;

button.Height=100;

button.FontSize=32;

button.Content="Startasynchronousoperations";

button.Click+=Click;

panel.Children.Add(_label);

panel.Children.Add(button);

win.Content=panel;

app.Run(win);

Console.ReadLine();

}

AthreadwherewecreatetheUIcontrolsmustbeaSingle-ThreadedApartmentthread,orSTA.ThistermcomesfromComponentObjectModel(COM)andisbasicallyrequiredfortheUImessagelooptobeabletointeractwithCOMcomponents.ManyOScomponents,suchassystemdialogs,usethistechnology.Tomakethingseasier,justrememberthattheUIthreadin.NETandWindowsmustbemarkedbytheSTAThreadattribute.

Then,wecreateseveralUIcontrols,composethemintheobjectmodel,andfinallyapp.Run(win)showstheapplicationwindowandstartsitsmessageloop:

asyncstaticvoidClick(objectsender,EventArgse)

{

_label.Content="Calculating…";

TimeSpanresultWithContext=awaitTest();

TimeSpanresultNoContext=awaitTestNoContext();

varsb=newStringBuilder();

sb.AppendLine(string.Format("Withthecontext:{0}",resultWithContext));

sb.AppendLine(string.Format("Withoutthecontext:{0}",

resultNoContext));

sb.AppendLine(string.Format("Ratio:{0:0.00}",

www.EBooksWorld.ir

Page 327: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

resultWithContext.TotalMilliseconds/

resultNoContext.TotalMilliseconds));

_label.Content=sb.ToString();

}

Thebuttonclickhandlerdoesaverysimplejob.Itrunstwoasynchronousoperations,getstheirresults,andthenoutputstheseresultstothelabelcontrolonthemainapplicationwindow.Sinceweusetheawaitstatement,wecanworkwiththeUIcontrolsfromthelattercode.

Nowtothemostimportantpartofthissample—asynchronousperformancetests:

asyncstaticTask<TimeSpan>Test()

{

constintiterationsNumber=100000;

varsw=newStopwatch();

sw.Start();

for(inti=0;i<iterationsNumber;i++)

{

vart=Task.Run(()=>{});

awaitt;

}

sw.Stop();

returnsw.Elapsed;

}

asyncstaticTask<TimeSpan>TestNoContext()

{

constintiterationsNumber=100000;

varsw=newStopwatch();

sw.Start();

for(inti=0;i<iterationsNumber;i++)

{

vart=Task.Run(()=>{});

awaitt.ConfigureAwait(continueOnCapturedContext:false);

}

sw.Stop();

returnsw.Elapsed;

}

Bothtestsdoalmostthesamething.Foralargenumberofiterations,theycreateatask,waitforitscompletion,andfinallyreturnthewholeamountoftimetakenbythetesttorun.ThesetwotestcodesaredifferentonlywithrespecttotheConfigureAwaitmethodusageinthesecondtestcode.However,thissubtledifferenceproducesahugeperformanceeffect.

Ifweruntheprogramandpressthebutton,wewillseequiteanoticeabledifferencebetweentestperformances.Onareferencemachine,thefirsttestisabouttentimesslowerthanthesecondone.However,ifyouruntheapplicationagainandthen,afterpressingthebutton,youstartresizingordraggingtheapplicationwindow,youwillnoticethatthefirsttestbecomesevenslower.Imanagedtomakeittwelvetimesslowerthanthesecondtest.

Theanswerissimple:thefirsttestusestheUIthreadtorunacontinuationcodeforeachoftheonehundredthousanditerations,thuspostingthesamenumberofmessagesonthe

www.EBooksWorld.ir

Page 328: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UImessageloop.Whenweresizeordragthemainapplicationwindow,weproduceothermessagesintheUIthatmakethemessagelooprunslower,andthetestbecomessloweraswell.ThisisdefinitelynotagoodpracticeandshouldbecontrolledusingtheConfigureAwaitmethodcall.

Thesecondtestusesthethreadpoolworkerthreadstopostitscontinuationcode.Sincethethreadpoolisverywelloptimizedforsmall,short-runningtasks,wegetgoodperformancehere.

NoteIfyouwritealibrarycode,alwaysbecarefultoavoidthesynchronizationcontext.Ifyourcontinuationcodedoesnotrequirethis,alwaysuseConfigureAwaittoturnoffthesynchronizationcontextflow.

Afterrunningtheprecedingcodesnippet,wegetthefollowingoutput:

Imaginethatthefirsttestisathird-partycodeandcannotbemodified.Canwedoanythingaboutthis?PeopleoftentrytouseConfigureAwaitasinthefollowingexample:

asyncstaticvoidClick(objectsender,EventArgse)

{

_label.Content="Calculating…";

vardispatcher=Dispatcher.CurrentDispatcher;

TimeSpanresultWithContext=awaitTest().ConfigureAwait(false);

TimeSpanresultNoContext=awaitTestNoContext();

varsb=newStringBuilder();

sb.AppendLine(string.Format("Withthecontext:{0}",resultWithContext));

sb.AppendLine(string.Format("Withoutthecontext:{0}",

resultNoContext));

sb.AppendLine(string.Format("Ratio:{0:0.00}",

resultWithContext.TotalMilliseconds/

www.EBooksWorld.ir

Page 329: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

resultNoContext.TotalMilliseconds));

dispatcher.Invoke(()=>

{

_label.Content=sb.ToString();

});

}

HerewehadtoslightlymodifythecodetobeabletoworkwiththeUIcontrolfromthethreadpoolworkerthread.BeawarethatifweusetheDispatcher.CurrentDispatcher.Invokemethodtosetthelabeltext,thecodewillfailbecauseallthehighlightedcoderunsinacontinuationofthefirstawaitstatement,andthusrunsonthethreadpool.So,herewehavetogetadispatcherreferencebeforerunningtheasynchronouscode.

However,nothinghaschangedfortheTestmethoditself.ItstillcapturesthecurrentcontextandusestheUIthreadtorunalltheiterations.Tobeabletofixthefirsttest,wehavetoswitchthesynchronizationcontexttothethreadpoolbeforewerunthistest.Asimpleworkaroundwilllooklikethis:

asyncstaticvoidClick(objectsender,EventArgse)

{

_label.Content="Calculating…";

vardispatcher=Dispatcher.CurrentDispatcher;

awaitTask.Delay(1).ConfigureAwait(false);

TimeSpanresultWithContext=awaitTest();

TimeSpanresultNoContext=awaitTestNoContext();

varsb=newStringBuilder();

sb.AppendLine(string.Format("Withthecontext:{0}",resultWithContext));

sb.AppendLine(string.Format("Withoutthecontext:{0}",

resultNoContext));

sb.AppendLine(string.Format("Ratio:{0:0.00}",

resultWithContext.TotalMilliseconds/

resultNoContext.TotalMilliseconds));

dispatcher.Invoke(()=>

{

_label.Content=sb.ToString();

});

}

Nowthefirsttestisinsidethecontinuationcode,whichrunsonthethreadpoolworkerthread,anditusesthethreadpoolsynchronizationcontext.Ifweruntheapplication,wewillseethatbothtestsperformmoreorlessequally.

Thistrickcanbeveryusefulwhendealingwithpoorlywrittenthird-partylibraries.Unfortunately,usuallysuchproblemsareveryhardtonoticeatfirstglance,andyoufindthemaccidentallyintheprofilerwhilelookingfortherootsofsomeotherproblems.

Afterrunningtheprecedingcodesnippet,wegetthefollowingoutput:

www.EBooksWorld.ir

Page 330: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 331: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 332: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,wehaveseentheimplementationdetailsoftraditionalWindowsapplicationUIsthatareusuallyhiddenbytheprogrammingplatformandhigh-levelUIframeworks.WehavelearnedaboutwhattheUIthreadandmessageloopare,andwhytheyareveryimportanttokeeptheUIthreadrunningandnotblockingitwithlong-runningcode.ThenwelearnedaboutthecommonproblemsofasynchronyintheUI,andhowtoavoiddeadlocksandmultithreadedaccesstotheUIcontrols’exceptions.

OneofthemostimportanttopicscoveredinthischapterwastheC#asynchronousinfrastructureinternals,showinghowtheawaitstatementworks,andhowwecanimproveapplicationperformancebychoosingnottokeepthecurrentsynchronizationcontext.

Inthenextchapter,wewilllookattroubleshootingconcurrentprogramsingreaterdetail.WewillknowaboutmanyexcitingfeaturesofVisualStudioforprofilinganddebuggingparallelprogramsandfindouthowtocatchmoreerrorsinthedevelopmentstagewiththehelpofunitandfunctionaltests.

www.EBooksWorld.ir

Page 333: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 334: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Chapter10.TroubleshootingParallelProgramsThischapterisdedicatedtoparallelprogramdebuggingspecifics.Wewillreviewhowconcurrentcodeisdifferent,whatadditionalproblemsweusuallyget,andwhatcanbedonetofindandfixbugseffectivelyinmultithreadedapplications.

www.EBooksWorld.ir

Page 335: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

HowtroubleshootingparallelprogramsisdifferentAconcurrentprogramlikeanyusualprogramcancontainprogrammingerrorsthatcouldleadtoincorrectresults.However,concurrencyusuallyleadsprogramstobecomemorecomplicated,causingerrorstobetrickierandhardertofind.AsmentionedinChapter1,TraditionalConcurrency,therearetypicalproblemsrelatedtoconcurrentsharedstateaccess—raceconditionsanddeadlocks,buttherearemanyotherkindsofproblemsspecifictoconcurrentprograms.Whilewewillnottrytodescribeeverykindofproblemindetail,sinceitwilltakeanotherbooktodothat,wewillinsteaddescribeseveraltechniquesthatallowustodetectandfixproblemsoverthedifferentstagesofworkingwithconcurrentprograms.

www.EBooksWorld.ir

Page 336: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

HeisenbugsThisisonemoreproblemtype,notstrictlyrelatedtoconcurrentprogramming,butmuchmorecommonwithit,usuallyreferredtoasheisenbug.ThistermisdefinedinWikipediaasfollows:

Incomputerprogrammingjargon,aheisenbugisasoftwarebugthatseemstodisappearoralteritsbehaviourwhenoneattemptstostudyit.ThetermisapunonthenameofWernerHeisenberg,thephysicistwhofirstassertedtheobservereffectofquantummechanics,whichstatesthattheactofobservingasysteminevitablyaltersitsstate.

Theseproblemsareusuallyextremelyhardtoreproduceanddebug,sincetheyusuallyappearinsomespecialconditionssuchashighuserload,orsomespecificeventstiming,andmore.Thisisthekindofbugwhichyouwillinevitablymeetwhiledevelopingconcurrentapplications.

Besideswhatwehavementionedsofar,concurrentprogramscanhaveproblemsrelatedtoinfrastructure,suchassynchronizationcontextsandUI,performanceproblems,oranyotherkindofproblems,whicharenotrelatedtoconcurrencyandmultithreadingatall.

Tomakeyourprogramlesserror-prone,youhavetouseacombinedapproachthatallowsthefindingandeliminationofbugsinthedifferentstagesofdevelopingyourapplicationfromwritingcodetoanalyzinglogsofproductiondeployment.Therearethreemainstagesthatarecrucialtocreaterobustandperformantapplications:

Writingtests:Thisisaveryimportantstepthatcandramaticallyreducebugsinyourcode.Withthesetests,itispossibletodetectproblemsrightafterwritingthecode,orafterdeployingyourapplicationintoatestenvironment.Debugging:VisualStudiohasspecificfeaturesandtoolstomakedebuggingconcurrentapplicationseasier.Performancemeasurementandprofiling:Thisisonemoreveryimportantstepthatcanhelptodetectwhetheryourprogramspendstoomuchtimeswitchingbetweenthreadsorblockingtheminsteadofdoingitsjob.

www.EBooksWorld.ir

Page 337: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 338: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

WritingtestsTestsallowustodetecterrorsattheveryearlystagesofdevelopment.Theyrequiresignificantinvestmentintermsoftimeandeffort,butinreturntheysavealotoftimethatcouldbelaterspentindebuggingtheapplication,whichisalwaysmuchharder.Therearedifferentkindsofteststhatcanhelptodetectdifferentproblemsintheapplication.

www.EBooksWorld.ir

Page 339: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

LoadtestsIfyourapplicationhastodealwithmultipleconcurrentusers,itislikelythatwiththeincreaseinthenumberofusers,youwillexperienceproblemsthatcannotberevealedinnormalconditions.Simulatingalargeuserloadandfurtherloganalysis,orstudyingprofilingresultsisalwaysagoodideaandapowerfultooltodetectpotentialpitfalls.

InChapter8,Server-SideAsynchrony,wereviewedacoupleofwaystoorganizealoadtest.Tosimulatereallylargeuseractivity,itcouldbenotenoughtouseasinglemachine.ItispossibletouseVisualStudioOnlinetorunaloadtestusingthepowerofMicrosoftAzuretorunseveralvirtualmachinesandusethemalltocreateatestloadforyourapplication.YouwillneedaVisualStudioOnlineaccount,andyouwillneedtosetaspecialflaginyourtestsettingsfile:

www.EBooksWorld.ir

Page 340: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UnittestsWithunittests,wecanperformtestsonsmallisolatedpartsofourcode.Forexample,ifwehaveanAsyncCounterclassthatcontainssomeconcurrentcountercalculations.Thefirstmethodcontainsaracecondition,whichleadstoincorrectcountervaluecalculation:

publicasyncTask<int>CountWithRaceConditionAsync()

{

constintiterations=10000;

varcounter=0;

Actioncount=

()=>

{

for(inti=0;i<iterations;i++)

{

counter++;

Thread.SpinWait(100);

counter--;

}

};

vartasks=

Enumerable

.Range(0,8)

.Select(n=>Task.Run(count))

.ToArray();

awaitTask.WhenAll(tasks);

returncounter;

}

ThesecondmethodisimplementedusingtheInterlockedoperations,andthusdoesnothaveproblemswithraceconditions:

publicasyncTask<int>CountWithInterlockedAsync()

{

constintiterations=10000;

varcounter=0;

Actioncount=

()=>

{

for(inti=0;i<iterations;i++)

{

Interlocked.Increment(refcounter);

Thread.SpinWait(100);

Interlocked.Decrement(refcounter);

}

};

vartasks=

Enumerable

.Range(0,8)

.Select(n=>Task.Run(count))

.ToArray();

awaitTask.WhenAll(tasks);

www.EBooksWorld.ir

Page 341: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

returncounter;

}

However,thefirstmethodcansometimesproducecorrectresults,soanincorrectimplementationcanmakeitswayintoaproductioncode.Topreventthisfromhappening,let’swriteatestthatrunscalculationsandcheckstheirresults.Towritetests,wewillusethestandardVisualStudiounittestprojectandtheVisualStudioUnitTestingFramework.Thetesttocheckthesecounterslookslikethis:

[TestClass]

publicclassCounterTests

{

[TestMethod]

publicasyncTaskTestCounterWithRaceCondition()

{

varcounter=newAsyncCounter();

intcount=awaitcounter.CountWithRaceConditionAsync();

Assert.AreEqual(0,count);

}

[TestMethod]

publicasyncTaskTestCounterWitInterlocked()

{

varcounter=newAsyncCounter();

intcount=awaitcounter.CountWithInterlockedAsync();

Assert.AreEqual(0,count);

}

}

NoticethatthetestmethodsaremarkedasasyncandreturnedasTask.Thisallowsustouseawaitinsidetests,andthisissupportedinallthemajormodernunittestingframeworks.TheTestClassattributeinformstheunittestingframeworkthatthisclasscontainsunittests,andTestMethodmarksasingletest.

Toruntests,wenavigatetotheTest|Run|AllTests…menuoption.ThenyouwillseetheTestExplorerwindowthatshowstheunittestresults.Theraceconditionunittestwillfail,becauseweexpectittoreturn0,butduetotheraceconditionitusuallyreturnssomeothernumber.Theothertestwillsucceed:

www.EBooksWorld.ir

Page 342: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Nowlet’strytowriteatestthatwilldetectdeadlocks.

First,wewillprepareanotherasynchronouslibrary,AsyncLib,thatcontainstwomethods.Thefirstmethodjustwaitsforonesecondandcompletessuccessfully.Thesecondonecontainsacodethatintentionallysimulatesdeadlock:

publicclassAsyncLib

{

publicasyncTaskGoodMethodAsync()

{

awaitTask.Delay(TimeSpan.FromSeconds(1));

}

publicasyncTaskDeadlockMethodAsync()

{

varlock1=newobject();

varlock2=newobject();

Tasktask1=Task.Run(()=>

{

lock(lock1)

{

Thread.Sleep(200);

lock(lock2)

{

}

}

});

Tasktask2=Task.Run(()=>

{

lock(lock2)

{

Thread.Sleep(200);

lock(lock1)

{

www.EBooksWorld.ir

Page 343: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

}

}

});

awaitTask.WhenAll(task1,task2);

}

}

Todetectadeadlock,wecanonlycheckwhetheranasynchronousmethodcallcompletesbeforeacertaintimeout.WecanaddanextensionmethodtoTaskthatwillhelpustosettheexpectedexecutiontimeoutvalueinmilliseconds.Afterthetimeoutexpires,wewillgetTimeoutExceptionifthetaskisnotcompleted:

publicstaticclassTaskExtensions

{

publicstaticasyncTaskTimeoutAfter(thisTasktask,

intmillisecondsTimeout)

{

if(task==awaitTask.WhenAny(task,

Task.Delay(millisecondsTimeout)))

{

awaittask;

}

else

{

thrownewTimeoutException();

}

}

}

Theunittestcodewillbeveryeasy—we’lljustaddaTimeoutAftermethodcalltoeachasynchronousfunction:

[TestClass]

publicclassLockTests

{

[TestMethod]

publicasyncTaskTestGoodAsync()

{

varlib=newAsyncLib();

awaitlib.GoodMethodAsync().TimeoutAfter(2000);

}

[TestMethod]

publicasyncTaskTestDeadlockAsync()

{

varlib=newAsyncLib();

awaitlib.DeadlockMethodAsync().TimeoutAfter(2000);

}

}

Asaresultofrunningthistest,wewillseethatwehavedetectedadeadlock:

www.EBooksWorld.ir

Page 344: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

VisualStudiohasanoptiontorununittestsaftereachbuild.Thiswillmakethebuildprocessslightlylonger,butwewillseethatunittestfailsaresimilartocompilationerrors.Thisisverycomfortableandhelpstoidentifyaproblemassoonaswewritethecode.TheVisualStudio2013UltimateeditionhasafeaturecalledCodeLensthatwillshowunittesterrorsrightbesidethecoderelatedtothetest:

www.EBooksWorld.ir

Page 345: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 346: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

IntegrationtestsAunittestisaverypowerfulconceptthatcanincreaseproductqualityandcanbeusedtofindmanybugsassoonastheyappearinthecode.However,whenyourapplicationbecomesmoreandmorecomplicated,testingseparatesmallcomponentsisnotenough.Manyproblemsappearwhenweusethesecomponentstogether,andwhiletwoasynchronousmethodscanrunwellseparately,theycancauseadeadlockwhilerunningsimultaneously.Thisiswhyitisveryimportanttowritehigher-leveltestsforyourapplicationthatruntheapplication’sbusinesslogicaltogether.Suchtestsarecalledintegrationtestsbecausewecheckhowtheapplicationcomponentsworktogether.

Toillustratethisapproach,wewilltakeaslightlychangedcodefromChapter8,Server-SideAsynchrony.ThisisanOWINWebAPIapplication,andwewilltestthiswithanHTTPAPIcontroller:

publicclassHomeController:ApiController

{

[HttpGet]

publicintSync()

{

varlib=newAsyncHttp();

returnlib.CountCharactersAsync(newUri("http://google.com")).Result;

}

[HttpGet]

publicasyncTask<int>Async()

{

varlib=newAsyncHttp();

returnawaitlib.CountCharactersAsync(newUri("http://google.com"));

}

}

Thiscontrollerlooksverysimple.However,inarealapplication,controllersareusuallytheplacesthatcontainapplicationlogic,andcontrolleractionscallseveralapplicationcomponentsandusetheresultstoprovidetheclientwiththedataneeded.Hereitisshownhowtowriteanintegrationtestforsuchacontroller,soyoucanusethisapproachwithyourcode.

ReferringtoChapter8,Server-SideAsynchrony,werememberthatthiscontrollerhasaproblem.Asynchronouscalltoanasynchronousmethodcouldresultinadeadlock.Solet’swriteatestthatwilllookforadeadlockhere.First,wewillneedtomodifytheTimeoutAfterextensionmethodtodealwiththeparameterizedTask<T>type.TheeasiestapproachistousethereactiveextensionsNuGetpackage.WewillneedtoreferencetheReactiveExtensions–CoreLibrarypackage.Then,wecanwritethefollowingcode:

publicstaticTask<T>TimeoutAfter<T>(thisTask<T>task,

intmillisecondsTimeout)

{

returntask.ToObservable().Timeout(

TimeSpan.FromMilliseconds(millisecondsTimeout)).ToTask();

www.EBooksWorld.ir

Page 347: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

}

Then,we’regoingtowritethetest.Firstofall,weneedtoreferencetheOWINWebAPINuGetpackage.Thenweneedtoaddonemorepackage,Microsoft.Owin.Testing,thathoststhewholeOWINapplicationinmemory.ThenwewillusethenewClassInitializeandClassCleanupattributestocreateatestserverandgetridofitwhenthetestscomplete:

[TestClass]

publicclassServerInMemoryTests

{

privatestaticTestServer_server;

privatestaticHttpClient_client;

[ClassInitialize]

publicstaticvoidClassInit(TestContextcontext)

{

_server=TestServer.Create<Startup>();

_client=_server.HttpClient;

}

[TestMethod]

publicasyncTaskTestSyncAction()

{

varresponse=await_client.GetAsync("/api/Home/Sync")

.TimeoutAfter(2000);

varresult=awaitresponse.Content.ReadAsAsync<int>();

Assert.IsTrue(result>0);

}

[TestMethod]

publicasyncTaskTestAsyncAction()

{

varresponse=await_client.GetAsync("/api/Home/Async")

.TimeoutAfter(2000);

varresult=awaitresponse.Content.ReadAsAsync<int>();

Assert.IsTrue(result>0);

}

[ClassCleanup]

publicstaticvoidClassCleanup()

{

_server.Dispose();

}

}

ThistestestablishestheOWINpipelineinmemoryandusesaregularHttpClientclasstosimulatehttpcallstoHomeControllerbyexpectingtogetagreaterthanzeronumber.

However,whenwerunthistest,wearegoingtofindoutthateverythingisfineandnodeadlockwillbefound:

www.EBooksWorld.ir

Page 348: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ThereasonwhythereisnodeadlockhereisthatthedeadlockwasrelatedtothesynchronizationcontextintheASP.NETenvironment,andthistestusedin-memoryhosting.However,wecandetecthereanyapplicationcomponent’sinteractionissues,andthiskindoftestisalsogoodtorunaftereachbuildinVisualStudio.

Todetectinfrastructureissues,wehavetotesttheapplicationbyrunningitinthesameenvironmentthatwillbeusedinproduction.Fortunately,thisisquiteeasytodo.Insteadofcreatinganin-memoryhost,wejustneedtorunourapplicationandslightlymodifythetestcodetousearealhttpinteraction:

[TestClass]publicclassServerHttpTests

{

privatestaticHttpClient_client;

[ClassInitialize]

publicstaticvoidClassInit(TestContextcontext)

{

_client=newHttpClient();

_client.BaseAddress=newUri("http://localhost:1845/");

}

[TestMethod]

publicasyncTaskTestSyncAction()

{

varresponse=await_client.GetAsync("/api/Home/Sync")

.TimeoutAfter(2000);

varresult=awaitresponse.Content.ReadAsAsync<int>();

Assert.IsTrue(result>0);

}

[TestMethod]

publicasyncTaskTestAsyncAction()

{

varresponse=await_client.GetAsync("/api/Home/Async")

.TimeoutAfter(2000);

www.EBooksWorld.ir

Page 349: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

varresult=awaitresponse.Content.ReadAsAsync<int>();

Assert.IsTrue(result>0);

}

[ClassCleanup]

publicstaticvoidClassCleanup()

{

_client.Dispose();

}

}

Noticethatthetestcoderemainsthesame.WehaveonlychangedtheHttpClientinstance.HerewejustpointittoourapplicationURL,andthisisallthatwehavechanged.Nowthetestdetectsadeadlockwhereweexpectedittooccur:

Thiskindoftestisnotintendedtoberunalongwiththebuilt-inVisualStudio.Theproperplacetorunthesetestsisyourcontinuousintegrationprocess,whenyoucreateanewbuildonyourbuildserver,deploytheapplicationintoatestenvironment,configureitandpre-populatedatastoragewithsometestdata,andthenrunatestsuiteonthisapplicationinstance.

TipThetestingstageisveryimportant,becauseitismuchhardertofindproblemsinthedebuggingorprofilingstage.Investingintestscanhelptosavealotoffurthereffortstofindoutwhatiswrongwiththeapplication,andgreatlyreducethenumberofproblemsthatgetintotheproductionenvironment.

www.EBooksWorld.ir

Page 350: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 351: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

DebuggingDebuggingasaveryextensivetopicandthereareseveralbooksaboutdebugging.NETapplicationstechniques.HerewewillreviewhowwecanstartdebuggingwithVisualStudio,andwhattoolscanhelpustodebugconcurrentapplications.

www.EBooksWorld.ir

Page 352: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

JustmycodesettingThereisaveryimportantsettinglocatedintheDebug,OptionsandSettings…menucalledEnableJustMyCode:

Whenthissettingisenabled,VisualStudiotriestohideadditionalinformationsuchascompiler-generatedcodeanddoesnotshowthisindebuggingwindows,concentratingonlyontheinformationrelatedtoyourcode.Thisseemscomfortable,butdonotforgetthatyoucanalwaysturnitoffandstudythewholepictureincaseyouneedtodigintotheinfrastructurecode.

www.EBooksWorld.ir

Page 353: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CallstackwindowOneoftheeasiestdebuggingtoolsinVisualStudioisthecallstackwindow.Anasynchronousmethodcallusuallyconsistsoftwoparts—beginandendoperation.Ifyouhaveabreakpointinsideanasynchronousmethodbody,itisnoteasytofindoutwherethisoperationhasbeeninitiated.Fortunately,ifyouhavethelatestVisualStudio2013installedatleastonWindows8.1orWindows2012R2,thecallstackwindowwillshowyouafullcallstackincludingtheasynchronousoperationstartingpoint.

Wemayrunthiscodeunderthedebugger,asfollows:

classProgram

{

staticvoidMain(string[]args)

{

StartAsyncOperation().GetAwaiter().GetResult();

}

publicstaticasyncTaskStartAsyncOperation()

{

Console.WriteLine("Startingasyncoperation");

awaitAsyncOperation();

Console.WriteLine("Afterfinishingasyncoperation");

}

publicstaticasyncTaskAsyncOperation()

{

Console.WriteLine("Insideasyncoperation");

awaitTask.Delay(TimeSpan.FromSeconds(1));

Console.WriteLine("Asyncoperationcomplete!");

}

}

Inthiscase,wewillseeinthecallstackwindowthattheoperationhasbeeninitiatedintheStartAsyncOperationmethod:

www.EBooksWorld.ir

Page 354: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 355: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ThreadswindowAnotherusefulVisualStudiodebuggingfeatureistheThreadswindow.Itshowsthecurrentthreadsintheapplicationandallowsustosuspendandresumeanythreadwithcorrespondingbuttonsandfilterthreadsbymarkingthemwithflagsandpressingthedoubleflagbutton:

www.EBooksWorld.ir

Page 356: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TaskswindowWiththeTaskswindow,itispossibletoreviewincompleteTPLtasksandseethedifferentinformationaboutthem:

TheTaskswindowhasdeadlockdiagnosticsthatinformusabouttasksthataredeadlocked.Toseeitinaction,wehavetorunthefollowingcodeuntiladeadlockoccursandthenpressthebreakbuttononthedebuggertoolbar:

classProgram

{

staticvoidMain(string[]args)

{

DeadlockMethodAsync().GetAwaiter().GetResult();

}

publicstaticasyncTaskDeadlockMethodAsync()

{

varlock1=newobject();

varlock2=newobject();

Tasktask1=Task.Run(()=>

{

lock(lock1)

{

Thread.Sleep(200);

www.EBooksWorld.ir

Page 357: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

lock(lock2)

{

}

}

});

Tasktask2=Task.Run(()=>

{

lock(lock2)

{

Thread.Sleep(200);

Debugger.Break();

lock(lock1)

{

}

}

});

Debugger.Break();

//hereyoucanopenTaskswindowinVisualStudio

awaitTask.WhenAll(task1,task2);

}

}

www.EBooksWorld.ir

Page 358: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ParallelstackswindowTovisualizeanasynchronousprogramflow,wecanusetheParallelStackswindow.Let’srunasimpleparallelforeachloop:

classProgram

{

staticvoidMain(string[]args)

{

ParallelForEach().GetAwaiter().GetResult();

}

publicstaticasyncTaskParallelForEach()

{

Parallel.ForEach(Enumerable.Range(0,32),i=>

{

Console.WriteLine(i);

if(i==24)

{

Debugger.Break();

}

Thread.Sleep(newRandom(i).Next(100,500));

});

}

}

ThisscreenshothasbeenmadeonavirtualmachinewithsixcoreCPUs.Weseethatoneofthetaskswasscheduledtorunonthemainthread:

www.EBooksWorld.ir

Page 359: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

IfweturnofftheEnableJustMyCodesetting,wewillseemoredetailsabouthowtheconcurrentprogramisorganized.

www.EBooksWorld.ir

Page 360: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 361: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

PerformancemeasurementandprofilingThereisaprofilerinVisualStudiothatcanbeusedtovisualizeconcurrencyinyourapplicationandseewhatisgoingon.DependingontheVisualStudioversion,itsbehaviorisdifferent.InVisualStudio2010,youwouldjustrunaprofilersessioncollectingconcurrencydataandgettherequiredresult.InVisualStudio2012,therewasaseparatemenuoptioncalledConcurrencyVisualizerandthisisthemostcomfortablewaytolookatconcurrencyprocessesinyourapplication.

InVisualStudio2013,thereisnoConcurrencyVisualizeroptionbydefault,andyoucanstillusetheregularprofilertocollectthebasicconcurrencyinformation.However,youcaninstallConcurrencyVisualizerseparately.

www.EBooksWorld.ir

Page 362: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TheConcurrencyVisualizerTheConcurrencyVisualizerisavailableforVisualStudio2013asaseparateextension.YoucaninstallitinVisualStudio:

Aftertheinstallation,youwillgetaConcurrencyVisualizermenuoptionundertheAnalyzemenuinVisualStudio:

ConcurrencyVisualizerprovidesalotofusefulinformation.Toillustratethis,let’scomparetheparallelismgranularitytestfromChapter3,UnderstandingParallelismGranularity,andI/OthreadsfromChapter8,Server-SideAsynchrony.ThefirstprogramunderConcurrencyVisualizerwilllooklikethis:

www.EBooksWorld.ir

Page 363: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

ItcanbeclearlyseenthattheprogramconsumesalotofCPUresources.NowifwevisualizeI/Othreads,wewillseethatitconsumesalmostnoCPUresources:

WecanseemoredetailsifwegototheThreadstabinsidethereport.ThegranularityprogramshowsasignificantCPUload,asshownhere:

www.EBooksWorld.ir

Page 364: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

However,theI/Othreadsreportindicatesthatabouthalfoftheprogramtimethreadsareintheblockedstate:

Thereisalotofdatainthisreport,andifyourunaprofilersessiontocollectconcurrencyinformation,youcangetevenmoredetails.Butthisisjustthestartingpointwhereyoucanseewhatisgoingonintheprogram,anddependingontheinformationreceivedinthereport,youcanfurtherinvestigatewhytheprogramdoesnotusealltheCPUcomputationalpower,orwhyitspendsalotoftimeinsynchronizationprocess.

www.EBooksWorld.ir

Page 365: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

www.EBooksWorld.ir

Page 366: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

SummaryInthischapter,welearnedaboutthedifferentstagesofapplicationdevelopmentwherewecandetectandfixproblemsinconcurrentapplications.Werevieweddifferenttestingtechniquesthathelptopreventbugsfromgettingintotheapplicationcode.Welearnedtouseasynchronousunittests,hostanOWINWebAPIapplicationinmemory,testHTTPAPIcontrollers,andalsoadapttheseteststorunontherealhttpapplicationhostedonawebserverinatestenvironment.

WehaverevieweddifferentdebuggingtoolsincludedinVisualStudio.Thesetoolshelpustovisualizetheconcurrentprogramworkflow,showinformationaboutcurrentlyrunningTPLtasks,detectdeadlocks,allowustopauseandresumethreads,seethedetailsofeachthread,andhelpustouseasynchronouscallstacksinacomfortableway,soitisclearwherethecurrentasynchronousoperationhasbeenstarted.

WealsoinstalledaConcurrencyVisualizerextensioninVisualStudio2013andusedittofindoutwhatisgoingonintheconcurrentapplication,howmuchtimetheapplicationspendssynchronizing,blockingthreads,anddoingCPU-boundwork.

www.EBooksWorld.ir

Page 367: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

IndexA

ABAproblemabout/TheABAproblem

acquirefenceabout/Memorymodelandcompileroptimizations

async/Unittestsasync/awaitinfrastructure

about/Concurrentidiomsasync/awaitstatements/AsynchronousProgrammingModelasynchronousI/O

about/DeepdiveintoasynchronousI/Oasynchronouspatterns

about/AsynchronouspatternsAsynchronousProgrammingModel(APM)/AsynchronousProgrammingModelEvent-basedAsynchronousPattern(EAP)/Event-basedAsynchronousPatternTask-basedAsynchronousPattern(TAP)/Task-basedAsynchronousPattern

AsynchronousProgrammingModel(APM)about/AsynchronousProgrammingModelfeatures/AsynchronousProgrammingModel

asynchrony,forUIimportance/TheimportanceofasynchronyforUI

asynckeywordabout/Istheasynckeywordreallyneeded?

asyncoversync/RealandfakeasynchronousI/Ooperationsatomic

about/TheSystem.Threading.Interlockedclassawait/Unittestsawaitingtaskcompletion

about/Awaitingtaskcompletionawaitkeyword

working/Howtheawaitkeywordworksawaitstatement/Settingatasktimeout

www.EBooksWorld.ir

Page 368: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

BBing

downloadingofimages,implementingfrom/ImplementingthedownloadingofimagesfromBingURL/ImplementingthedownloadingofimagesfromBing

blockingqueue/CustomProducer/Consumerpatternimplementationboundedqueue/CustomProducer/Consumerpatternimplementation

www.EBooksWorld.ir

Page 369: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

CC#5.0built-insupport,forasynchrony

code,enhancingwith/EnhancingthecodewithC#5.0built-insupportforasynchrony

C#asynchronousinfrastructuresimulating,withiterators/SimulatingC#asynchronousinfrastructurewithiterators

cacheimplementing,withReaderWriterLockSlim/ImplementingacachewithReaderWriterLockSlim

cacheasidepattern/ImplementingacachewithReaderWriterLockSlimcallbacks

used,fortaskcancellation/Cancellationusingcallbacksclassconstraint

about/Thelock-freestackcoarse-grainedapproach

about/Understandinggranularityselecting/Choosingthecoarse-grainedorfine-grainedapproach

coarse-grainedapproach,withTPLabout/Latencyandthecoarse-grainedapproachwithTPL

coarse-grainedlocking/Concurrentcollectionsin.NETcodecoupling

about/Taskcompositioncommonproblems

about/Commonproblemsandsolutionssolutions/Commonproblemsandsolutions

compare-and-swap(CAS)/Implementationdetailscompareandswap(CAS)

about/TheABAproblemcompileroptimizations

about/MemorymodelandcompileroptimizationsComponentObjectModel(COM)/PerformanceissuesConcurrencyVisualizer

about/TheConcurrencyVisualizerConcurrentBag<T>

about/ConcurrentBag<T>using/ConcurrentBaginpractice

ConcurrentDictionaryabout/ConcurrentDictionaryLazy<T>/UsingLazy<T>details,implementing/Implementationdetailsimplementationdetails,using/Usingtheimplementationdetailsinpractice

ConcurrentDictionaryclasslock-freeoperations/Implementationdetails,Lock-freeoperationsfine-grainedlockoperations/Implementationdetails,Fine-grainedlock

www.EBooksWorld.ir

Page 370: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

operationsexclusivelockoperations/Implementationdetails,Exclusivelockoperations

concurrentidiomsabout/ConcurrentidiomsProcessTasks,inCompletionOrder/ProcessTasksinCompletionOrderparallelismdegree,limiting/Limitingtheparallelismdegreetasktimeout,setting/Settingatasktimeout

concurrentpatternsabout/Concurrentpatterns

ConcurrentQueue<T>about/ConcurrentQueue<T>

ConcurrentStack<T>about/ConcurrentStack<T>

continuationtaskabout/Taskcomposition

CPU-boundtasksandqueuesabout/CPU-boundtasksandqueues

customawaitabletypeimplementing/Implementingacustomawaitabletype

CustomProviderclass/ImplementingacachewithReaderWriterLockSlim

www.EBooksWorld.ir

Page 371: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Ddeadlock

about/What’stheproblem?debugging

about/DebuggingEnableJustMyCodesetting/Justmycodesettingcallstackwindow/Callstackwindowthreadswindow/ThreadswindowTaskswindow/Taskswindowparallelstackswindow/Parallelstackswindow

doublecheckedlockingpatternabout/Thelock-freequeue

downloadingofimages,implementingfromBingabout/ImplementingthedownloadingofimagesfromBingsimplesynchronoussolution,creating/Creatingasimplesynchronoussolutionparallelsolution,creatingwithTaskParallelLibrary/CreatingaparallelsolutionwithTaskParallelLibrarycode,enhancingwithC#5.0built-insupport/EnhancingthecodewithC#5.0built-insupportforasynchronyC#asynchronousinfrastructure,simulatingwithiterators/SimulatingC#asynchronousinfrastructurewithiterators

www.EBooksWorld.ir

Page 372: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

EEditBin.exetool/ThenumberofthreadsEnqueuemethod/ConcurrentQueue<T>Event-basedAsynchronousPattern(EAP)

about/Event-basedAsynchronousPatternfeatures/Event-basedAsynchronousPattern

exceptionhandlingabout/Exceptionhandling

exclusivelockoperations/Exclusivelockoperationsexecutioncontext/Executionandsynchronizationcontexts

www.EBooksWorld.ir

Page 373: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

FfakeasynchronousI/Ooperations

about/RealandfakeasynchronousI/Ooperationsfeatures,AsynchronousProgrammingModel(APM)

low-levelpattern/AsynchronousProgrammingModellowperformanceoverhead/AsynchronousProgrammingModelcomplicatedimplementation/AsynchronousProgrammingModelcoupling,betweenasynchronousoperationproviderandconsumer/AsynchronousProgrammingModel

features,Event-basedAsynchronousPattern(EAP)high-levelpattern/Event-basedAsynchronousPatternhighoverhead/Event-basedAsynchronousPatternintendedforUIcomponents/Event-basedAsynchronousPatterncomplicatedimplementation/Event-basedAsynchronousPatterncouping,betweenasynchronousoperationproviderandconsumers/Event-basedAsynchronousPattern

features,Task-basedAsynchronousPattern(TAP)lowoverhead/Task-basedAsynchronousPatternhigh-level/Task-basedAsynchronousPatterncomfortabletouse/Task-basedAsynchronousPatternlanguagesupport,inC#/VB/Task-basedAsynchronousPatternTaskandTask<T>arefirst-classobjects/Task-basedAsynchronousPatternavoidance,ofsideeffects/Task-basedAsynchronousPattern

fine-grainedapproachabout/Understandinggranularityselecting/Choosingthecoarse-grainedorfine-grainedapproach

fine-grainedlockoperations/Fine-grainedlockoperationsfire-and-forgettasks

about/Fire-and-forgettasksforeachloop/ProcessTasksinCompletionOrderfork/joinpattern

about/Concurrentpatternsfuture

about/Taskcomposition

www.EBooksWorld.ir

Page 374: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

G4-gigabytetuning/Thenumberofthreadsgranularity

about/Understandinggranularity

www.EBooksWorld.ir

Page 375: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Hheisenbugs

about/Heisenbugshighcoupling

about/TaskcompositionHyper-Threadingtechnology/Thenumberofthreadshyperthreadingtechnology/TheimportanceofasynchronyforUI

www.EBooksWorld.ir

Page 376: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

II/OandCPU-boundtasks/I/OandCPU-boundtasksI/OCompletionPort(IOCP)/I/OandCPU-boundtasksinput/outputthreads/Usingthethreadpoolintegrationtests

about/Integrationtestsinterlockedinternals

working/Interlockedinternals

www.EBooksWorld.ir

Page 377: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Llatencyapproach,withTPL

about/Latencyandthecoarse-grainedapproachwithTPLlegacycodesupportscenario/Event-basedAsynchronousPatternloadtesting/Loadtestingandscalabilitylock-freecode

writing/Writinglock-freecodelock-freeoperations/Lock-freeoperationslock-freequeue

about/Thelock-freequeuelock-freestack

about/Thelock-freestacklocklocalization

about/Locklocalizationlocks

using/Usinglockslockstatement

about/Lockstatement/Standardcollectionsandsynchronizationprimitiveslowcoupling

about/Taskcomposition

www.EBooksWorld.ir

Page 378: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Mmemorybarrier

about/System.Threading.SpinLock,Memorymodelandcompileroptimizationsmemorymodel

about/Memorymodelandcompileroptimizationsmessageloop/UIthreadsandmessageloopsmessagepump/UIthreadsandmessageloopsMonitorclass

about/Monitorclassmutexsynchronizationprimitive

about/Thelock-freestack

www.EBooksWorld.ir

Page 379: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

N.NET

concurrentcollections/Concurrentcollectionsin.NET.NET4.0+

Producer/Consumerpattern/TheProducer/Consumerpatternin.NET4.0+

www.EBooksWorld.ir

Page 380: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Oonlyforlegacycodesupportscenario/AsynchronousProgrammingModeloptimizationstrategy

about/Optimizationstrategylocklocalization/Locklocalizationshareddataminimization/Shareddataminimization

OSwaitobjectsusing,withWaitHandle/UsingOSwaitobjectswithWaitHandle

OWINWebAPIframeworkabout/TheOWINWebAPIframework

www.EBooksWorld.ir

Page 381: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

PParallelclass

using/UsingtheParallelclassParallel.Invokemethod/Parallel.InvokeParallel.Formethod/Parallel.ForandParallel.ForeachParallel.Foreachmethod/Parallel.ForandParallel.Foreach

parallelpipelineabout/Concurrentpatternsimplementing/Parallelpipelines

parallelprogramstroubleshooting/Howtroubleshootingparallelprogramsisdifferentheisenbugs/Heisenbugs

parallelsolutioncreating,withTaskParallelLibrary/CreatingaparallelsolutionwithTaskParallelLibrary

performanceissues/Performanceissuesperformancemeasurement

about/PerformancemeasurementandprofilingPLINQ/Concurrentcollectionsin.NETproducer/consumerpattern

about/ConcurrentpatternsProducer/Consumerpattern

about/TheProducer/Consumerpatternimplementing/CustomProducer/Consumerpatternimplementationin.NET4.0+/TheProducer/Consumerpatternin.NET4.0+

profilingabout/Performancemeasurementandprofiling

promiseabout/Taskcomposition

www.EBooksWorld.ir

Page 382: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Rracecondition

about/What’stheproblem?sample/What’stheproblem?

/Unittestsreaderwriterlock

about/Reader-writerlockReaderWriterLockSlim

used,forimplementingcache/ImplementingacachewithReaderWriterLockSlim

readyqueueabout/Monitorclass

realasynchronousI/Ooperationsabout/RealandfakeasynchronousI/Ooperations

releasefenceabout/Memorymodelandcompileroptimizations

replicabletask/Parallel.Invokerobustandperformantapplications

creating,stages/HeisenbugsRunLongRunningOperationmethod/ConcurrentDictionary

www.EBooksWorld.ir

Page 383: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Sscalability/Serverapplications,Loadtestingandscalabilityserverapplication

about/Serverapplicationstypes/Serverapplicationsscalability/Serverapplicationsscalevertically/Serverapplicationshorizontalscalability/Serverapplications

shareddataminimizationabout/Shareddataminimization

simplesynchronoussolutioncreating/Creatingasimplesynchronoussolution

Single-ThreadedApartment(STA)/Performanceissuesspinlock

about/SpinlockThread.SpinWait/Thread.SpinWaitSystem.Threading.SpinWait/System.Threading.SpinWaitSystem.Threading.SpinLock/System.Threading.SpinLock

standardcollections/Standardcollectionsandsynchronizationprimitivessynchronizationcontext

about/Synchronizationcontextsynchronizationcontexts/Executionandsynchronizationcontextssynchronizationprimitives/StandardcollectionsandsynchronizationprimitivesSystem.Threading.Interlockedclass

about/TheSystem.Threading.InterlockedclassSystem.Threading.SpinLock

about/System.Threading.SpinLockSystem.Threading.SpinWait

about/System.Threading.SpinWaitSystem.Threading.Taskclass/Understandinggranularity

www.EBooksWorld.ir

Page 384: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

TTablesclass

m_buckets/Implementationdetailsm_locks/Implementationdetailsm_countPerLock/Implementationdetailsm_comparer/Implementationdetails

Task-basedAsynchronousPattern(TAP)about/Task-basedAsynchronousPatternfeatures/Task-basedAsynchronousPattern

taskcancellationabout/Taskcancellationflag,checking/Checkingaflagexception,throwing/ThrowinganexceptionOSwaitobjects,usingwithWaitHandle/UsingOSwaitobjectswithWaitHandlewithcallbacks/Cancellationusingcallbacks

taskcompositionabout/Taskcomposition

TaskParallelLibraryparallelsolution,creatingwith/CreatingaparallelsolutionwithTaskParallelLibrary

TaskParallelLibrary(TPL)about/Understandinggranularity

TaskParallelLibrary,featuresabout/OtherusefulTPLfeaturesTask.Delay/Task.DelayTask.Yield/Task.Yield

taskschedulerabout/Taskcomposition,Understandingthetaskscheduler

taskshierarchyabout/Taskshierarchy

testswriting/Writingtestsloadtests/Loadtestsunittests/Unittests

Thread.SpinWaitabout/Thread.SpinWait

threadcontention/Standardcollectionsandsynchronizationprimitivesthreadpool

using/Usingthethreadpoolthreads

overview/Thenumberofthreads

www.EBooksWorld.ir

Page 385: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

UUIthread/UIthreadsandmessageloops

www.EBooksWorld.ir

Page 386: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Vvolatile

about/Memorymodelandcompileroptimizationsvolatilekeyword

about/Memorymodelandcompileroptimizationsvolatileread

about/Memorymodelandcompileroptimizationsvolatilewrite

about/Memorymodelandcompileroptimizations

www.EBooksWorld.ir

Page 387: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

WWaitHandle

OSwaitobjects,usingwith/UsingOSwaitobjectswithWaitHandlewaitingqueue

about/Monitorclasswhileloop/Fine-grainedlockoperations,ProcessTasksinCompletionOrderWindowsForms

about/Creatingasimplesynchronoussolutionworkerthreads/Usingthethreadpoolworkstealing/ConcurrentBag<T>

www.EBooksWorld.ir

Page 388: Mastering C# Concurrency - EBooksWorld...a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at  for more details

Xxampp

about/Loadtestingandscalability

www.EBooksWorld.ir