.NET: Innealan airson a bhith ag obair le multithreading agus asynchrony. Pàirt 1

Tha mi a 'foillseachadh an artaigil tùsail air Habr, agus tha an eadar-theangachadh air a phostadh anns a' chorporra post blog.

Bha feum air rudeigin a dhèanamh asyncronach, gun a bhith a 'feitheamh ris a' bhuil an seo agus an-dràsta, no a bhith a 'roinn obair mhòr eadar grunn aonadan ga choileanadh, mus tàinig coimpiutairean. Nuair a thàinig iad a-steach, dh'fhàs am feum seo gu math follaiseach. A-nis, ann an 2019, tha mi a ’taipeadh an artaigil seo air laptop le pròiseasar 8-core Intel Core, air a bheil còrr air ceud pròiseas a’ ruith aig an aon àm, agus eadhon barrachd snàithleanan. Faisg air làimh, tha fòn beagan dubharach, a chaidh a cheannach o chionn bliadhna no dhà, tha pròiseasar 8-cridhe aige air bòrd. Tha goireasan cuspaireil làn artaigilean agus bhideothan far am bi na h-ùghdaran aca a’ toirt urram do phrìomh fhònaichean sgairteil na bliadhna-sa anns a bheil pròiseasairean 16-cridhe. Tha MS Azure a’ toirt seachad inneal brìgheil le pròiseasar bunaiteach 20 agus 128 TB RAM airson nas lugha na $ 2 / uair. Gu mì-fhortanach, tha e do-dhèanta an ìre as àirde a thoirt a-mach agus an cumhachd seo a chleachdadh gun a bhith comasach air eadar-obrachadh snàithleanan a riaghladh.

Briathrachas

Pròiseas - Ann an nì OS, àite seòlaidh iomallach, tha snàithleanan ann.
Snàthainn - nì OS, an aonad coileanaidh as lugha, pàirt de phròiseas, bidh snàithleanan a’ roinn cuimhne agus goireasan eile am measg iad fhèin taobh a-staigh pròiseas.
Multitasking - seilbh OS, an comas grunn phròiseasan a ruith aig an aon àm
Ioma-cridhe - seilbh den phròiseasar, an comas grunn choraichean a chleachdadh airson giullachd dàta
Ioma-phròiseasadh - seilbh coimpiutair, an comas a bhith ag obair gu corporra le grunn phròiseasan aig an aon àm
Ioma-snàthainn - seilbh pròiseas, an comas giollachd dàta a sgaoileadh am measg grunn snàithleanan.
Co-shìnteachd - a’ coileanadh grunn ghnìomhan aig an aon àm gu corporra gach aonad ùine
Asioncronach - coileanadh gnìomhachd gun a bhith a’ feitheamh ris a’ ghiollachd seo a chrìochnachadh; faodar toradh a’ chur gu bàs a phròiseasadh nas fhaide air adhart.

Meafar

Chan eil a h-uile mìneachadh math agus tha feum aig cuid air mìneachadh a bharrachd, agus mar sin cuiridh mi metafhor mu bhith a’ còcaireachd bracaist ris a’ bhriathrachas a chaidh a thoirt a-steach gu foirmeil. Is e pròiseas a th’ ann a bhith a’ còcaireachd bracaist anns a’ mheafar seo.

Nuair a bhios mi ag ullachadh bracaist sa mhadainn bidh mi (CPU) Thig mi dhan chidsin (Coimpiutair). Tha 2 làmh agam (eitein). Tha grunn innealan anns a’ chidsin (IO): àmhainn, coire, tostar, refrigerator. Bidh mi a’ tionndadh an gas, a’ cur pana-fhrithealaidh air agus a’ dòrtadh ola a-steach ann gun a bhith a’ feitheamh gus an teas e (asyncronach, Neo-Bacadh-IO-Fuirich), Bidh mi a’ toirt na h-uighean a-mach às an fhrigeradair agus gam briseadh a-steach do phlàta, an uairsin gam bualadh le aon làimh (Snàthainn #1), agus an dàrna (Snàthainn #2) a’ cumail a’ phlàta (Goireas Co-roinnte). A-nis bu mhath leam an coire a thionndadh air, ach chan eil làmhan gu leòr agam (Starvation nan snàthadan) Rè na h-ùine seo, bidh am pras-frithealaidh a 'teasachadh suas (A' làimhseachadh an toraidh) far am bi mi a 'dòrtadh na tha mi air a chuipeadh. Bidh mi a’ ruighinn airson a’ choire agus ga thionndadh air agus a’ coimhead gu gòrach air an uisge a ghoil ann (Bacadh-IO-Fuirich).

Bhruich mi omelet le bhith a 'cleachdadh dìreach 2 làmhan, agus chan eil barrachd agam, ach aig an aon àm, nuair a chaidh an omelet a chuipeadh, chaidh 3 obraichean a dhèanamh aig an aon àm: a' crathadh an omelet, a 'cumail a' phlàta, a 'teasachadh a' phras-fhrithealaidh. Is e an CPU am pàirt as luaithe den choimpiutair, is e IO mar as trice a bhios a h-uile càil a’ slaodadh sìos, agus mar sin gu tric is e fuasgladh èifeachdach a bhith a’ gabhail thairis an CPU le rudeigin fhad ‘s a tha thu a’ faighinn dàta bho IO.

A’ leantainn leis a’ mheafar:

  • Nam biodh mi ann a bhith ag ullachadh omelet, bhithinn cuideachd a’ feuchainn ri aodach atharrachadh, bhiodh seo na eisimpleir de ioma-obair. Naidheachd chudromach: tha coimpiutairean tòrr nas fheàrr air an seo na daoine.
  • Cidsin le grunn chòcairean, mar eisimpleir ann an taigh-bìdh - coimpiutair ioma-cridhe.
  • Mòran thaighean-bìdh ann an cùirt bìdh ann an ionad bhùthan - ionad dàta

Innealan .NET

Tha .NET math air obrachadh le snàithleanan, mar le iomadh rud eile. Le gach dreach ùr, bidh e a’ toirt a-steach barrachd is barrachd innealan ùra airson a bhith ag obair còmhla riutha, sreathan ùra de tharraing thairis air snàithleanan OS. Nuair a bhios iad ag obair le togail tarraingean, bidh luchd-leasachaidh frèam a 'cleachdadh dòigh-obrach a dh' fhàgas an cothrom, nuair a bhios iad a 'cleachdadh tarraing àrd-ìre, a dhol sìos aon ìre no barrachd gu h-ìosal. Mar as trice chan eil seo riatanach, gu dearbh bidh e a ’fosgladh an dorais gu bhith a’ losgadh ort fhèin sa chas le gunna-gunna, ach uaireannan, ann an cùisean ainneamh, is dòcha gur e seo an aon dòigh air fuasgladh fhaighinn air duilgheadas nach eil air fhuasgladh aig an ìre tarraing a th ’ann an-dràsta. .

Le innealan, tha mi a 'ciallachadh an dà chuid eadar-aghaidh prògramadh tagraidh (APIs) air a thoirt seachad leis an fhrèam agus pasganan treas-phàrtaidh, a bharrachd air fuasglaidhean bathar-bog slàn a nì sìmplidh air rannsachadh airson duilgheadasan sam bith co-cheangailte ri còd ioma-snàithleach.

A 'tòiseachadh air snàithlean

Is e an clas Thread an clas as bunaitiche ann an .NET airson obrachadh le snàithleanan. Gabhaidh an neach-togail ri aon de dhà riochdaire:

  • ThreadStart - Gun chrìochan
  • ParametrizedThreadStart - le aon paramadair de sheòrsa nì.

Thèid an riochdaire a chuir gu bàs anns an t-snàthainn ùr a chaidh a chruthachadh às deidh an dòigh tòiseachaidh a ghairm Ma chaidh riochdaire den t-seòrsa ParametrizedThreadStart a chuir chun neach-togail, feumaidh rud a dhol chun mhodh tòiseachaidh. Tha feum air an uidheamachd seo gus fiosrachadh ionadail sam bith a ghluasad chun t-sruth. Is fhiach a bhith mothachail gur e obair daor a th’ ann a bhith a’ cruthachadh snàithlean, agus gur e rud trom a th’ anns an t-snàthainn fhèin, co-dhiù leis gu bheil e a’ riarachadh 1MB de chuimhne air a’ chruach agus gu feum e eadar-obrachadh leis an OS API.

new Thread(...).Start(...);

Tha an clas ThreadPool a’ riochdachadh bun-bheachd amar. Ann an .NET, is e pìos innleadaireachd a th’ anns an amar snàithlean, agus tha an luchd-leasachaidh aig Microsoft air tòrr oidhirp a dhèanamh gus dèanamh cinnteach gu bheil e ag obair mar as fheàrr ann am measgachadh farsaing de shuidheachaidhean.

Bun-bheachd coitcheann:

Bhon mhionaid a thòisicheas an tagradh, bidh e a ’cruthachadh grunn snàithleanan ann an tèarmann air a’ chùl agus a ’toirt comas an toirt airson an cleachdadh. Ma thèid snàithleanan a chleachdadh gu tric agus ann an àireamhan mòra, leudaichidh an linne gus coinneachadh ri feumalachdan an neach-fios. Nuair nach eil snàithleanan an-asgaidh anns an amar aig an àm cheart, fuirichidh e gus an till aon de na snàithleanan, no cruthaichidh e fear ùr. Tha e a’ leantainn gu bheil an cruinneachadh snàithlean sgoinneil airson cuid de ghnìomhan geàrr-ùine agus gu math freagarrach airson gnìomhachd a tha a’ ruith mar sheirbheisean air feadh gnìomhachd iomlan an tagraidh.

Gus snàithlean bhon amar a chleachdadh, tha modh QueueUserWorkItem ann a ghabhas ri riochdaire den t-seòrsa WaitCallback, aig a bheil an aon ainm-sgrìobhte ri ParametrizedThreadStart, agus tha am paramadair a thèid thuige a’ coileanadh an aon ghnìomh.

ThreadPool.QueueUserWorkItem(...);

Tha an dòigh amar snàithlean nach eil cho aithnichte RegisterWaitForSingleObject air a chleachdadh gus gnìomhachd IO nach eil a’ bacadh a chuir air dòigh. Thèid an riochdaire a thèid a chuir chun dòigh seo a ghairm nuair a thèid an WaitHandle chun dòigh “Foillseachadh”.

ThreadPool.RegisterWaitForSingleObject(...)

Tha timer snàithlean aig .NET agus tha e eadar-dhealaichte bho timers WinForms/WPF leis gun tèid an inneal-làimhseachaidh aige a ghairm air snàithlean a chaidh a thoirt bhon linne.

System.Threading.Timer

Tha dòigh car annasach ann cuideachd riochdaire a chuir airson a chur gu bàs gu snàithlean bhon linne - an dòigh BeginInvoke.

DelegateInstance.BeginInvoke

Bu mhath leam fuireach goirid air a’ ghnìomh ris an canar mòran de na dòighean gu h-àrd - CreateThread bho Kernel32.dll Win32 API. Tha dòigh ann, le taing do uidheamachd dhòighean bhon taobh a-muigh, gus an gnìomh seo a ghairm. Chan fhaca mi a leithid de ghairm ach aon turas ann an eisimpleir uamhasach de chòd dìleab, agus tha brosnachadh an ùghdair a rinn dìreach seo fhathast na dhìomhaireachd dhomh.

Kernel32.dll CreateThread

A’ coimhead agus a’ dì-bhugachadh snàithleanan

Faodar snàithleanan a chruthaich thu, a h-uile co-phàirt treas-phàrtaidh, agus an linne .NET fhaicinn ann an uinneag Threads de Visual Studio. Cha seall an uinneag seo ach fiosrachadh snàthainn nuair a tha an aplacaid fo debug agus ann am modh Break. An seo faodaidh tu coimhead gu goireasach air ainmean stac agus prìomhachasan gach snàithlean, agus tionndadh debugging gu snàithlean sònraichte. A’ cleachdadh seilbh Prìomhachais a’ chlas Thread, faodaidh tu prìomhachas snàithlean a shuidheachadh, a chì an OC agus CLR mar mholadh nuair a bhios tu a’ roinn ùine pròiseasar eadar snàithleanan.

.NET: Innealan airson a bhith ag obair le multithreading agus asynchrony. Pàirt 1

Leabharlann gnìomhan co-shìnte

Chaidh Leabharlann Task Parallel (TPL) a thoirt a-steach ann an .NET 4.0. A-nis is e an inbhe agus am prìomh inneal airson a bhith ag obair le asyncronaidh. Thathas den bheachd gu bheil còd sam bith a chleachdas dòigh-obrach nas sine mar dhìleab. Is e aonad bunaiteach TPL an clas Gnìomh bhon ainm-àite System.Threading.Tasks. Is e gnìomh tarraing thairis air snàithlean. Leis an dreach ùr den chànan C #, fhuair sinn dòigh eireachdail air obrachadh le Tasks - async / feitheamh ri gnìomhaichean. Rinn na bun-bheachdan sin e comasach còd asyncronach a sgrìobhadh mar gum biodh e sìmplidh agus sioncronaich, rinn seo e comasach eadhon do dhaoine aig nach robh mòran tuigse air obrachadh a-staigh snàithleanan tagraidhean a sgrìobhadh a bhios gan cleachdadh, tagraidhean nach reothadh nuair a bhios iad a’ coileanadh obrachaidhean fada. Tha cleachdadh async / feitheamh na chuspair airson aon no eadhon grunn artaigilean, ach feuchaidh mi ri brìgh a thoirt dha ann am beagan sheantansan:

  • tha async na mhion-atharrachadh air dòigh a’ tilleadh Task no falamh
  • agus feitheamh tha gnìomhaiche feitheimh Task nach eil a’ bacadh.

A-rithist: bidh an gnìomhaiche feitheamh, sa chùis choitcheann (tha eisgeachdan ann), a 'leigeil a-mach an t-snàthainn cur gu bàs a th' ann an-dràsta, agus nuair a bhios an Gnìomh a 'crìochnachadh a chur gu bàs, agus an t-snàthainn (gu dearbh, bhiodh e na bu chòir an co-theacsa a ràdh , ach barrachd air sin nas fhaide air adhart) a’ leantainn air adhart a’ cur an gnìomh an dòigh nas fhaide. Taobh a-staigh .NET, tha an dòigh seo air a chuir an gnìomh san aon dòigh ri toradh toraidh, nuair a thionndaidheas an dòigh sgrìobhte gu clas slàn, a tha na inneal stàite agus faodar a chuir gu bàs ann am pìosan fa leth a rèir nan stàitean sin. Faodaidh neach sam bith le ùidh còd sìmplidh sam bith a sgrìobhadh le bhith a’ cleachdadh asynс/wait, an co-chruinneachadh a chur ri chèile agus fhaicinn a’ cleachdadh JetBrains dotPeek le Còd Gineadh Compiler air a chomasachadh.

Bheir sinn sùil air roghainnean airson Gnìomh a chuir air bhog agus a chleachdadh. Anns an eisimpleir còd gu h-ìosal, bidh sinn a’ cruthachadh gnìomh ùr nach dèan dad feumail (Thread.Sleep(10000)), ach ann am fìor bheatha bu chòir seo a bhith na obair iom-fhillte CPU-dian.

using TCO = System.Threading.Tasks.TaskCreationOptions;

public static async void VoidAsyncMethod() {
    var cancellationSource = new CancellationTokenSource();

    await Task.Factory.StartNew(
        // Code of action will be executed on other context
        () => Thread.Sleep(10000),
        cancellationSource.Token,
        TCO.LongRunning | TCO.AttachedToParent | TCO.PreferFairness,
        scheduler
    );

    //  Code after await will be executed on captured context
}

Tha gnìomh air a chruthachadh le grunn roghainnean:

  • Tha LongRunning na bheachd nach tèid an obair a chrìochnachadh gu sgiobalta, agus tha sin a’ ciallachadh gur dòcha gum b ’fhiach beachdachadh air gun a bhith a’ toirt snàithlean bhon amar, ach a ’cruthachadh fear air leth airson a’ ghnìomh seo gus nach dèan thu cron air feadhainn eile.
  • AttachedToParent - Faodar gnìomhan a chuir air dòigh ann an rangachd. Ma chaidh an roghainn seo a chleachdadh, is dòcha gum bi an Task ann an staid far a bheil e fhèin air a chrìochnachadh agus a 'feitheamh ri cur gu bàs a cuid chloinne.
  • PreferFairness - a’ ciallachadh gum biodh e na b’ fheàrr Gnìomhan a chuir an gnìomh a chuir an gnìomh na bu thràithe ron fheadhainn a chaidh a chuir nas fhaide air adhart. Ach is e dìreach moladh a tha seo agus chan eil toraidhean cinnteach.

Is e an dàrna paramadair a chaidh a thoirt don dòigh CancellationToken. Gus dèiligeadh gu ceart ri cuir às do ghnìomhachd às deidh dha tòiseachadh, feumaidh an còd a thèid a chuir an gnìomh a lìonadh le seicichean airson an stàit CancellationToken. Mura h-eil sgrùdaidhean ann, an uairsin bidh an dòigh Sguir dheth ris an canar an nì CancellationTokenSource comasach air stad a chuir air coileanadh na h-obrach dìreach mus tòisich e.

Tha am paramadair mu dheireadh na nì clàr-ama den t-seòrsa TaskScheduler. Tha an clas seo agus a shliochd air an dealbhadh gus ro-innleachdan a riaghladh airson gnìomhan a sgaoileadh thairis air snàithleanan; gu gnàthach, thèid an Gnìomh a chuir gu bàs air snàithlean air thuaiream bhon amar.

Tha an gnìomhaiche feitheamh air a chuir an sàs anns a ’ghnìomh a chaidh a chruthachadh, a tha a’ ciallachadh gun tèid an còd a chaidh a sgrìobhadh às a dhèidh, ma tha fear ann, a chuir gu bàs san aon cho-theacsa (gu tric bidh seo a ’ciallachadh air an aon snàithlean) ris a’ chòd mus feitheamh thu.

Tha an dòigh air a chomharrachadh mar falamh async, a tha a’ ciallachadh gun urrainn dha an gnìomhaiche feitheamh a chleachdadh, ach cha bhith e comasach don chòd gairm feitheamh airson a chuir gu bàs. Ma tha feum air a leithid de fheart, feumaidh an dòigh Task a thilleadh. Tha dòighean comharraichte async falamh gu math cumanta: mar riaghailt, is iad sin luchd-làimhseachaidh tachartais no dòighean eile a bhios ag obair air prionnsapal teine ​​​​is dìochuimhneachadh. Ma dh'fheumas tu chan ann a-mhàin a bhith a 'toirt cothrom feitheamh gu deireadh a' chur gu bàs, ach cuideachd an toradh a thilleadh, feumaidh tu Task a chleachdadh.

Air a ’ghnìomh a thill an dòigh StartNew, a bharrachd air dòigh sam bith eile, faodaidh tu an dòigh ConfigureAwait a ghairm leis a’ paramadair meallta, an uairsin leanaidh coileanadh às deidh feitheamh chan ann air a ’cho-theacsa a chaidh a ghlacadh, ach air fear neo-riaghailteach. Bu chòir seo a dhèanamh an-còmhnaidh nuair nach eil an co-theacsa cur gu bàs cudromach airson a’ chòd às deidh feitheamh. Tha seo cuideachd na mholadh bho MS nuair a bhios tu a’ sgrìobhadh còd a thèid a lìbhrigeadh ann am pasgan ann an leabharlann.

Gabhamaid beagan a bharrachd a thaobh mar as urrainn dhut feitheamh airson crìoch a chur air Gnìomh. Gu h-ìosal tha eisimpleir de chòd, le beachdan air cuin a thèid an dùil a dhèanamh gu math agus nuair a thèid a dhèanamh gu dona.

public static async void AnotherMethod() {

    int result = await AsyncMethod(); // good

    result = AsyncMethod().Result; // bad

    AsyncMethod().Wait(); // bad

    IEnumerable<Task> tasks = new Task[] {
        AsyncMethod(), OtherAsyncMethod()
    };

    await Task.WhenAll(tasks); // good
    await Task.WhenAny(tasks); // good

    Task.WaitAll(tasks.ToArray()); // bad
}

Anns a ’chiad eisimpleir, bidh sinn a’ feitheamh ris a ’ghnìomh a chrìochnachadh gun a bhith a’ cur bacadh air an t-snàthainn gairm; tillidh sinn gu bhith a’ giullachd an toraidh dìreach nuair a tha e ann mu thràth; gu ruige sin, bidh an t-snàthainn gairm air fhàgail aig na h-innealan aige fhèin.

Anns an dàrna roghainn, bidh sinn a’ bacadh an t-snàthainn gairm gus an tèid toradh an dòigh a thomhas. Tha seo dona chan ann a-mhàin air sgàth gu bheil sinn air snàithlean a ghabhail thairis, goireas cho luachmhor sa phrògram, le idleness sìmplidh, ach cuideachd air sgàth ma tha còd an dòigh ris an can sinn a’ feitheamh, agus feumaidh an co-theacsa sioncronaidh tilleadh chun t-snàthainn gairm às deidh sin. feitheamh, an uairsin gheibh sinn stad-stad: Tha an t-snàthainn gairm a’ feitheamh ri toradh an dòigh asyncronach a bhith air a thomhas, bidh an dòigh asyncronach a’ feuchainn gu dìomhain ri leantainn air adhart le bhith ga chur an gnìomh anns an t-snàthainn gairm.

Is e eas-bhuannachd eile den dòigh-obrach seo làimhseachadh mhearachdan iom-fhillte. Is e an fhìrinn gu bheil mearachdan ann an còd asyncronach nuair a bhios tu a’ cleachdadh async / feitheamh gu math furasta an làimhseachadh - bidh iad gan giùlan fhèin mar gum biodh an còd sioncronaich. Fhad ‘s ma chuireas sinn exorcism feitheamh sioncronaich an sàs ann an Gnìomh, bidh an eisgeachd tùsail a’ tionndadh gu bhith na AggregateException, i.e. Gus an eisgeachd a làimhseachadh, feumaidh tu sgrùdadh a dhèanamh air an t-seòrsa InnerException agus sèine ma tha thu fhèin taobh a-staigh aon bhloc glacaidh a sgrìobhadh no an glacadh a chleachdadh nuair a thèid a thogail, an àite an t-sreath de bhlocaichean glacaidh a tha nas eòlaiche san t-saoghal C #.

Tha an treas agus na h-eisimpleirean mu dheireadh cuideachd air an comharrachadh dona airson an aon adhbhar agus tha na h-aon dhuilgheadasan ann.

Tha na dòighean WhenAny and WhenAll air leth freagarrach airson a bhith a’ feitheamh ri buidheann de ghnìomhan; bidh iad a’ pasgadh buidheann de ghnìomhan ann an aon, a loisgeas an dàrna cuid nuair a thèid Gnìomh bhon bhuidheann a phiobrachadh an toiseach, no nuair a bhios iad uile air an cur gu bàs.

A 'cur stad air snàithleanan

Airson diofar adhbharan, is dòcha gum feumar stad a chuir air an t-sruth às deidh dha tòiseachadh. Tha grunn dhòighean ann seo a dhèanamh. Tha dà dhòigh air an ainmeachadh gu h-iomchaidh aig a’ chlas Thread: Giorrachadh и Cuir stad air. Chan eil a 'chiad fhear air a mholadh gu mòr airson a chleachdadh, oir às deidh dha a ghairm aig àm air thuaiream sam bith, nuair a thathar a’ giullachd stiùireadh sam bith, thèid eisgeachd a thilgeil ThreadAbortedException. Chan eil thu an dùil gun tèid an leithid de dh’ eisgeachd a thilgeil nuair a bhios tu ag àrdachadh caochladair iomlan, ceart? Agus nuair a bhios tu a 'cleachdadh an dòigh seo, is e suidheachadh fìor a tha seo. Ma dh’ fheumas tu casg a chuir air an CLR bho bhith a’ gineadh eisgeachd mar sin ann an earrann sònraichte den chòd, faodaidh tu a phasgadh ann an gairmean Thread.BeginCriticalRegion, Thread.EndCriticalRegion. Tha còd sam bith sgrìobhte ann am bloc mu dheireadh air a phasgadh ann an leithid de ghairmean. Air an adhbhar seo, ann an doimhneachd a 'chòd frèam gheibh thu blocaichean le feuchainn falamh, ach chan e falamh mu dheireadh. Tha Microsoft a’ dì-mhisneachadh an dòigh seo cho mòr is nach do chuir iad a-steach e ann an cridhe .net.

Bidh an dòigh Interrupt ag obair nas ro-innseach. Faodaidh e casg a chuir air an t-snàthainn le eisgeachd ThreadInterruptedException dìreach aig na h-amannan sin nuair a tha an t-snàthainn ann an staid feitheimh. Bidh e a 'tighinn a-steach don stàit seo fhad' sa tha e a 'crochadh fhad' sa tha e a 'feitheamh ri WaitHandle, glas, no an dèidh dha Thread.Sleep a ghairm.

Tha an dà roghainn a tha air am mìneachadh gu h-àrd dona air sgàth cho neo-fhaicsinneach. Is e am fuasgladh structar a chleachdadh CancellationToken agus clas CancellationTokenSource. Is e seo a’ phuing: tha eisimpleir den chlas CancellationTokenSource air a chruthachadh agus chan urrainn ach an neach leis a bheil e stad a chuir air an obair le bhith a’ gairm an dòigh Sguir dheth. Is e dìreach an CancellationToken a thèid a thoirt don ghnìomhachd fhèin. Chan urrainn do shealbhadairean CancellationToken an gnìomhachd a chuir dheth iad fhèin, ach chan urrainn dhaibh ach dèanamh cinnteach an deach an gnìomhachd a chuir dheth. Tha seilbh Boolean ann airson seo IsCancellationRequest agus modh ThrowIfCancel air iarraidh. Tilgidh an tè mu dheireadh eisgeachd TaskCancelledException ma chaidh an dòigh Sguir dheth a ghairm air an eisimpleir CancellationToken a bhith air a parrot. Agus is e seo an dòigh a tha mi a 'moladh a chleachdadh. Is e leasachadh a tha seo air na roghainnean a bh’ ann roimhe le bhith a’ faighinn làn smachd air cuin a dh’ fhaodar stad a chuir air gnìomhachd eisgeachd.

Is e an roghainn as brùideil airson stad a chuir air snàithlean an gnìomh Win32 API TermminateThread a ghairm. Dh’ fhaodadh gum bi giùlan an CLR às deidh an gnìomh seo a ghairm do-chreidsinneach. Air MSDN tha na leanas sgrìobhte mun ghnìomh seo: “Is e gnìomh cunnartach a th’ ann an TerminThread nach bu chòir a chleachdadh ach anns na cùisean as miosa. “

Ag atharrachadh dìleab API gu Gnìomh Stèidhichte a’ cleachdadh modh FromAsync

Ma bha thu fortanach a bhith ag obair air pròiseact a chaidh a thòiseachadh às deidh gnìomhan a thoirt a-steach agus nach do chuir thu às do uabhas sàmhach don mhòr-chuid de luchd-leasachaidh, cha bhith agad ri dèiligeadh ri mòran de sheann APIan, an dà chuid feadhainn treas-phàrtaidh agus an sgioba agad. air a chràdh san àm a dh'fhalbh. Gu fortanach, thug sgioba .NET Framework aire dhuinn, ged is dòcha gur e an amas aire a thoirt dhuinn fhìn. Biodh sin mar a dh’ fhaodadh e, tha grunn innealan aig .NET airson còd a thionndadh gun phian a chaidh a sgrìobhadh ann an seann dhòighean prògramadh asyncronach ris an fhear ùr. Is e aon dhiubh an dòigh FromAsync de TaskFactory. Anns an eisimpleir còd gu h-ìosal, bidh mi a’ pasgadh seann dhòighean async a’ chlas WebRequest ann an Gnìomh a’ cleachdadh an dòigh seo.

object state = null;
WebRequest wr = WebRequest.CreateHttp("http://github.com");
await Task.Factory.FromAsync(
    wr.BeginGetResponse,
    we.EndGetResponse
);

Is e dìreach eisimpleir a tha seo agus chan eil e coltach gum feum thu seo a dhèanamh le seòrsachan togte, ach tha seann phròiseact sam bith dìreach a’ fàs làn de dhòighean BeginDoSomething a thilleas IAsyncResult agus EndDoSomething dòighean a gheibh e.

Tionndaidh API dìleab gu Stèidhichte air Gnìomh a’ cleachdadh clas TaskCompletionSource

Is e inneal cudromach eile airson beachdachadh air a’ chlas TaskCompletionSource. A thaobh gnìomhan, adhbhar agus prionnsapal obrachaidh, is dòcha gu bheil e rudeigin a’ cur nar cuimhne modh RegisterWaitForSingleObject den chlas ThreadPool, a sgrìobh mi mu dheidhinn gu h-àrd. A’ cleachdadh a’ chlas seo, is urrainn dhut seann APIan asyncronach a phasgadh ann an Tasks gu furasta agus gu goireasach.

Canaidh tu gu bheil mi air bruidhinn mu thràth mun dòigh FromAsync den chlas TaskFactory a tha san amharc airson na h-adhbharan sin. An seo feumaidh sinn cuimhneachadh air an eachdraidh iomlan mu leasachadh mhodalan asyncronach ann an .net a tha Microsoft air a thabhann thairis air na 15 bliadhna a dh’ fhalbh: ron phàtran asyncronach stèidhichte air gnìomhan (TAP), bha am Pàtran Prògramadh Asyncronach (APP), a bha e mu dheidhinn dòighean-obrach TòisichDèan rudeigin a’ tilleadh Toradh IAsync agus dòighean-obrach EndDoSomething a ghabhas ris agus airson dìleab nam bliadhnaichean seo tha an dòigh FromAsync dìreach foirfe, ach thar ùine, chaidh am Pàtran Asyncronach Stèidhichte air Tachartas a chuir na àite (AGUS AP), a bha a’ gabhail ris gun deidheadh ​​tachartas a thogail nuair a bhiodh an obair asyncronach deiseil.

Tha TaskCompletionSource foirfe airson gnìomhan a phasgadh agus APIan dìleab a chaidh a thogail timcheall air modal an tachartais. Tha brìgh na h-obrach aige mar a leanas: tha seilbh poblach aig nì den chlas seo den t-seòrsa Task, agus faodar smachd a chumail air an staid tro dhòighean SetResult, SetException, msaa den chlas TaskCompletionSource. Ann an àiteachan far an deach an gnìomhaiche feitheamh a chuir an sàs anns a’ ghnìomh seo, thèid a chuir gu bàs no fàiligeadh le eisgeachd a rèir an dòigh a chaidh a chuir an sàs anns an TaskCompletionSource. Mura h-eil e soilleir fhathast, leig dhuinn sùil a thoirt air an eisimpleir còd seo, far a bheil cuid de sheann EAP API air a phasgadh ann an Gnìomh a’ cleachdadh TaskCompletionSource: nuair a thèid an tachartas a losgadh, thèid an Gnìomh a ghluasad chun staid Crìochnaichte, agus an dòigh a chuir an gnìomhaiche feitheamh an sàs chun na Gnìomha seo tòisichidh e air a choileanadh às deidh dha an nì fhaighinn thoradh air.

public static Task<Result> DoAsync(this SomeApiInstance someApiObj) {

    var completionSource = new TaskCompletionSource<Result>();
    someApiObj.Done += 
        result => completionSource.SetResult(result);
    someApiObj.Do();

    result completionSource.Task;
}

Molaidhean & cleasan TaskCompletionSource

Chan e a bhith a’ pasgadh seann APIan a h-uile rud a ghabhas dèanamh a’ cleachdadh TaskCompletionSource. Le bhith a’ cleachdadh a’ chlas seo tha cothrom inntinneach ann diofar APIan a dhealbhadh air Gnìomhan nach eil air an cleachdadh le snàithleanan. Agus tha an t-sruth, mar a chuimhnicheas sinn, na ghoireas daor agus tha an àireamh aca cuingealaichte (gu sònraichte leis an ìre de RAM). Faodar an cuingealachadh seo a choileanadh gu furasta le bhith a’ leasachadh, mar eisimpleir, tagradh lìn luchdaichte le loidsig gnìomhachais iom-fhillte. Beachdaichidh sinn air na cothroman air a bheil mi a’ bruidhinn nuair a bhios mi a’ cur an gnìomh cleas leithid Long-bhòtaidh.

Ann an ùine ghoirid, is e seo brìgh a ’chleas: feumaidh tu fiosrachadh fhaighinn bhon API mu chuid de thachartasan a tha a’ tachairt air a thaobh, fhad ‘s nach urrainn don API, airson adhbhar air choireigin, cunntas a thoirt air an tachartas, ach chan urrainn dha ach an stàit a thilleadh. Is e eisimpleir dhiubh sin a h-uile API a chaidh a thogail air mullach HTTP ro amannan WebSocket no nuair a bha e do-dhèanta airson adhbhar air choireigin an teicneòlas seo a chleachdadh. Faodaidh an neach-dèiligidh faighneachd don fhrithealaiche HTTP. Chan urrainn don fhrithealaiche HTTP e fhèin conaltradh a thòiseachadh leis an neach-dèiligidh. Is e fuasgladh sìmplidh a bhith a’ sgrùdadh an fhrithealaiche a’ cleachdadh timer, ach tha seo a’ cruthachadh eallach a bharrachd air an fhrithealaiche agus dàil a bharrachd air cuibheasachd TimerInterval / 2. Gus faighinn timcheall air an seo, chaidh cleas ris an canar Long Polling a chruthachadh, a tha a’ toirt a-steach dàil a chuir air freagairt an fhrithealaiche. frithealaiche gus an tig an Timeout gu crìch no gun tachair tachartas. Ma tha an tachartas air tachairt, tha e air a phròiseasadh, mura h-eil, thèid an t-iarrtas a chuir a-rithist.

while(!eventOccures && !timeoutExceeded)  {

  CheckTimout();
  CheckEvent();
  Thread.Sleep(1);
}

Ach bidh fuasgladh mar seo uamhasach cho luath ‘s a bhios an àireamh de luchd-dèiligidh a tha a’ feitheamh ris an tachartas ag èirigh, leis gu bheil ... Bidh gach neach-dèiligidh mar sin a’ fuireach ann an snàithlean slàn a’ feitheamh ri tachartas. Tha, agus gheibh sinn dàil 1ms a bharrachd nuair a thèid an tachartas a bhrosnachadh, mar as trice chan eil seo cudromach, ach carson a nì am bathar-bog nas miosa na dh’ fhaodadh e a bhith? Ma bheir sinn air falbh Thread.Sleep(1), an uairsin gu dìomhain luchdaichidh sinn aon phrìomh phròiseasar 100% leisg, a’ tionndadh ann an cearcall gun fheum. A’ cleachdadh TaskCompletionSource is urrainn dhut an còd seo ath-dhèanamh gu furasta agus na duilgheadasan gu h-àrd fhuasgladh:

class LongPollingApi {

    private Dictionary<int, TaskCompletionSource<Msg>> tasks;

    public async Task<Msg> AcceptMessageAsync(int userId, int duration) {

        var cs = new TaskCompletionSource<Msg>();
        tasks[userId] = cs;
        await Task.WhenAny(Task.Delay(duration), cs.Task);
        return cs.Task.IsCompleted ? cs.Task.Result : null;
    }

    public void SendMessage(int userId, Msg m) {

        if (tasks.TryGetValue(userId, out var completionSource))
            completionSource.SetResult(m);
    }
}

Chan eil an còd seo deiseil airson riochdachadh, ach dìreach demo. Gus a chleachdadh ann an cùisean fìor, feumaidh tu cuideachd, aig a ’char as lugha, dèiligeadh ris an t-suidheachadh nuair a ruigeas teachdaireachd aig àm nuair nach eil dùil aig duine ris: anns a’ chùis seo, bu chòir don dòigh AsseptMessageAsync Gnìomh a chaidh a chrìochnachadh mu thràth a thilleadh. Mas e seo a’ chùis as cumanta, faodaidh tu smaoineachadh air ValueTask a chleachdadh.

Nuair a gheibh sinn iarrtas airson teachdaireachd, bidh sinn a’ cruthachadh agus a’ cur TaskCompletionSource san fhaclair, agus an uairsin a’ feitheamh airson na thachras an toiseach: thig an ùine ainmichte gu crìch no gheibhear teachdaireachd.

ValueTask: carson agus ciamar

Bidh na gnìomhaichean async / feitheamh, mar an gnìomhaiche tilleadh toraidh, a’ gineadh inneal stàite bhon dòigh, agus is e seo cruthachadh nì ùr, nach eil cha mhòr an-còmhnaidh cudromach, ach ann an cùisean ainneamh faodaidh e duilgheadas a chruthachadh. Is dòcha gur e a ’chùis seo dòigh ris an canar gu math tric, tha sinn a’ bruidhinn mu dheidhinn deichean is ceudan de mhìltean de ghairmean gach diog. Ma tha an leithid de dhòigh air a sgrìobhadh ann an dòigh a bhios sa mhòr-chuid de chùisean a ’tilleadh toradh a’ dol seachad air a h-uile modh feitheamh, an uairsin tha .NET a ’toirt seachad inneal gus seo a bharrachadh - structar ValueTask. Gus a dhèanamh soilleir, leig dhuinn sùil a thoirt air eisimpleir de a chleachdadh: tha tasgadan ann air am bi sinn a’ dol gu math tric. Tha cuid de luachan ann agus an uairsin bidh sinn dìreach gan tilleadh; mura h-eil, thèid sinn gu IO slaodach airson am faighinn. Tha mi airson an tè mu dheireadh a dhèanamh asyncronach, a tha a’ ciallachadh gu bheil an dòigh gu lèir a ’tionndadh a-mach gu bhith asyncronach. Mar sin, tha an dòigh follaiseach air an dòigh a sgrìobhadh mar a leanas:

public async Task<string> GetById(int id) {

    if (cache.TryGetValue(id, out string val))
        return val;
    return await RequestById(id);
}

Air sgàth miann beagan a mheudachadh, agus beagan eagal air na bhios Roslyn a ’gineadh nuair a bhios tu a’ cur a ’chòd seo ri chèile, faodaidh tu an eisimpleir seo ath-sgrìobhadh mar a leanas:

public Task<string> GetById(int id) {

    if (cache.TryGetValue(id, out string val))
        return Task.FromResult(val);
    return RequestById(id);
}

Gu dearbh, is e am fuasgladh as fheàrr sa chùis seo an t-slighe teth a bharrachadh, is e sin, luach fhaighinn bhon fhaclair gun riarachadh neo-riatanach agus luchdachadh air an GC, agus anns na cùisean tearc sin nuair a dh’ fheumas sinn fhathast a dhol gu IO airson dàta. , bidh a h-uile càil na bhuannachd / nas lugha san t-seann dòigh:

public ValueTask<string> GetById(int id) {

    if (cache.TryGetValue(id, out string val))
        return new ValueTask<string>(val);
    return new ValueTask<string>(RequestById(id));
}

Bheir sinn sùil nas mionaidiche air a’ phìos còd seo: ma tha luach anns an tasgadan, cruthaichidh sinn structar, air neo bidh an fhìor ghnìomh air a phasgadh ann am fear brìoghmhor. Chan eil dragh aig a’ chòd gairm dè an t-slighe anns an deach an còd seo a chuir gu bàs: bidh ValueTask, bho shealladh co-chòrdadh C #, gad ghiùlan fhèin mar an ceudna ri gnìomh àbhaisteach sa chùis seo.

TaskSchedulers: a’ riaghladh ro-innleachdan cur air bhog gnìomh

Is e an ath API a bu mhath leam beachdachadh air a’ chlas Clàr-gnothaich agus na toraidhean aige. Thug mi iomradh mu thràth gu h-àrd gu bheil comas aig TPL ro-innleachdan a riaghladh airson gnìomhan a sgaoileadh thairis air snàithleanan. Tha ro-innleachdan mar seo air am mìneachadh ann an sliochd a’ chlas TaskScheduler. Tha cha mhòr ro-innleachd sam bith a dh’ fheumas tu ri lorg anns an leabharlann. Leudachain Co-shìnte a bharrachd, air a leasachadh le Microsoft, ach chan eil e na phàirt de .NET, ach air a thoirt seachad mar phacaid Nuget. Bheir sinn sùil ghoirid air cuid dhiubh:

  • Clàr-ama gnàthach ThreadTask - cuir an gnìomh gnìomhan air an t-snàthainn gnàthach
  • LimitedConcurrencyLevelTaskScheduler - a’ cuingealachadh an àireamh de ghnìomhan a thèid a choileanadh aig an aon àm le paramadair N, ris an gabh an neach-togail
  • ÒrdaichteTaskScheduler - air a mhìneachadh mar LimitedConcurrencyLevelTaskScheduler(1), agus mar sin thèid gnìomhan a choileanadh ann an òrdugh.
  • WorkStealingTaskScheduler - innealan obair-goid dòigh-obrach airson sgaoileadh ghnìomhan. Gu bunaiteach is e ThreadPool air leth a th’ ann. A’ fuasgladh na trioblaid gur e clas statach a th’ ann an .NET ThreadPool, aon airson a h-uile tagradh, a tha a’ ciallachadh gum faod cus cuideim no cleachdadh ceàrr ann an aon phàirt den phrògram leantainn gu fo-bhuaidhean ann am fear eile. A bharrachd air an sin, tha e gu math duilich a bhith a 'tuigsinn adhbhar nan uireasbhaidhean sin. Sin. Dh’ fhaodadh gum bi feum air WorkStealingTaskSchedulers air leth a chleachdadh ann am pàirtean den phrògram far am faodadh cleachdadh ThreadPool a bhith ionnsaigheach agus neo-fhaicsinneach.
  • QueuedTaskScheduler - a’ leigeil leat gnìomhan a dhèanamh a rèir riaghailtean ciudha prìomhachais
  • ThreadPerTaskScheduler - a’ cruthachadh snàithlean air leth airson gach gnìomh a thèid a choileanadh air. Faodaidh e a bhith feumail airson gnìomhan a bheir ùine gun dùil airson a chrìochnachadh.

Tha mion-fhiosrachadh math ann artaigil mu TaskSchedulers air blog microsoft.

Airson dì-bhugachadh goireasach air a h-uile càil co-cheangailte ri Tasks, tha uinneag Tasks aig Visual Studio. Anns an uinneag seo chì thu suidheachadh làithreach na h-obrach agus leumaidh tu chun loidhne chòd a tha a’ coileanadh an-dràsta.

.NET: Innealan airson a bhith ag obair le multithreading agus asynchrony. Pàirt 1

PLinq agus an clas Co-shìnte

A bharrachd air Tasks agus a h-uile càil a chaidh a ràdh mun deidhinn, tha dà inneal nas inntinniche ann an .NET: PLinq (Linq2Parallel) agus an clas Co-shìnte. Tha a’ chiad fhear a’ gealltainn coileanadh co-shìnte de ghnìomhachd Linq air iomadh snàithlean. Faodar an àireamh snàithnean a rèiteachadh a’ cleachdadh modh leudachaidh WithDegreeOfParallelism. Gu mì-fhortanach, mar as trice chan eil fiosrachadh gu leòr aig PLinq anns a’ mhodh àbhaisteach aige mu na tha taobh a-staigh an stòr dàta agad gus buannachd astar mòr a thoirt seachad, air an làimh eile, tha cosgais feuchainn glè ìosal: feumaidh tu fios a chuir gu modh AsParallel roimhe seo. an t-sreath de dhòighean Linq agus ruith deuchainnean coileanaidh. A bharrachd air an sin, tha e comasach fiosrachadh a bharrachd a chuir gu PLinq mu nàdar an stòr dàta agad a’ cleachdadh an uidheamachd Partitions. Faodaidh tu barrachd a leughadh an seo и an seo.

Tha an clas statach Co-shìnte a’ toirt seachad dòighean airson ath-aithris tro chruinneachadh Foreach ann an co-shìnte, a’ cur an gnìomh lùb For, agus a’ cur an gnìomh grunn riochdairean ann an Invoke co-shìnte. Thèid stad a chuir air coileanadh an t-snàthainn gnàthach gus an tèid an àireamhachadh a chrìochnachadh. Faodar an àireamh snàithnean a rèiteachadh le bhith a’ dol seachad air ParallelOptions mar an argamaid mu dheireadh. Faodaidh tu cuideachd TaskScheduler agus CancellationToken a shònrachadh a’ cleachdadh roghainnean.

toraidhean

Nuair a thòisich mi a’ sgrìobhadh an artaigil seo stèidhichte air stuthan na h-aithisge agam agus am fiosrachadh a chruinnich mi fhad ‘s a bha mi ag obair às a dhèidh, cha robh dùil agam gum biodh uimhir dheth ann. A-nis, nuair a dh’ innseas an deasaiche teacsa anns a bheil mi a’ taipeadh an artaigil seo gu brònach dhomh gu bheil duilleag 15 air falbh, bheir mi geàrr-chunntas air na toraidhean eadar-amail. Thèid cleasan eile, APIan, innealan lèirsinneach agus duilgheadasan a chòmhdach san ath artaigil.

Co-dhùnaidhean:

  • Feumaidh tu a bhith eòlach air na h-innealan airson a bhith ag obair le snàithleanan, asyncronach agus co-shìnteachd gus goireasan PCan an latha an-diugh a chleachdadh.
  • Tha mòran innealan eadar-dhealaichte aig .NET airson na h-adhbharan sin
  • Cha do nochd iad uile aig an aon àm, agus mar sin gheibh thu gu tric feadhainn dìleabach, ge-tà, tha dòighean ann seann APIan a thionndadh gun mòran oidhirp.
  • Tha obrachadh le snàithleanan ann an .NET air a riochdachadh leis na clasaichean Thread and ThreadPool
  • Tha na dòighean Thread.Abort, Thread.Interrupt, agus Win32 API TermminateThread cunnartach agus chan eilear gam moladh airson an cleachdadh. An àite sin, tha e nas fheàrr an inneal CancellationToken a chleachdadh
  • Tha sruth na ghoireas luachmhor agus chan eil mòran ri fhaighinn. Bu chòir suidheachaidhean far a bheil snàithleanan trang a’ feitheamh ri tachartasan a sheachnadh. Airson seo tha e goireasach an clas TaskCompletionSource a chleachdadh
  • Is e na h-innealan .NET as cumhachdaiche agus as adhartaiche airson a bhith ag obair le co-shìnteachd agus asyncronach Gnìomhan.
  • Bidh na gnìomhaichean c# async/await a’ buileachadh a’ bhun-bheachd air feitheamh gun bhacadh
  • Faodaidh tu smachd a chumail air cuairteachadh ghnìomhan thar snàithleanan a’ cleachdadh chlasaichean a thig bho TaskScheduler
  • Faodaidh structar ValueTask a bhith feumail ann a bhith ag àrdachadh slighean teth agus trafaic cuimhne
  • Tha uinneagan Tasks and Threads aig Visual Studio a’ toirt seachad tòrr fiosrachaidh a tha feumail airson còd ioma-snàithlean no asyncronach a dhì-bhugachadh
  • Is e inneal fionnar a th’ ann am PLinq, ach is dòcha nach eil fiosrachadh gu leòr aige mun stòr dàta agad, ach faodar seo a shocrachadh a’ cleachdadh an uidheamachd dealachaidh
  • Ri leantainn ...

Source: www.habr.com

Cuir beachd ann