.NET: Uirlisí chun oibriú le multithreading agus asynchrony. Cuid 1

Tá an t-alt bunaidh ar Habr á fhoilsiú agam, agus tá an t-aistriúchán ar fáil sa chorparáid post blag.

An gá atá le rud éigin a dhéanamh asynchronously, gan fanacht leis an toradh anseo agus anois, nó obair mhór a roinnt i measc na n-aonad éagsúla a bhí á dhéanamh, ann roimh theacht na ríomhairí. Le teacht orthu, tháinig an riachtanas seo an-inláimhsithe. Anois, i 2019, táim ag clóscríobh an ailt seo ar ríomhaire glúine le próiseálaí Intel Core 8-lárnach, ar a bhfuil níos mó ná céad próiseas ag rith go comhthreomhar, agus fiú níos mó snáitheanna. In aice láimhe, tá fón beagán shabby, a cheannaigh cúpla bliain ó shin, tá próiseálaí 8-lárnach ar bord. Tá acmhainní téamacha lán d’ailt agus d’fhíseáin a bhfuil meas ag a n-údair ar fhóin chliste shuaitheanta na bliana seo ina bhfuil próiseálaithe croí-16. Soláthraíonn MS Azure meaisín fíorúil le próiseálaí croí 20 agus 128 TB RAM ar feadh níos lú ná $ 2 / uair. Ar an drochuair, tá sé dodhéanta an t-uasmhéid a bhaint as agus leas a bhaint as an gcumhacht seo gan a bheith in ann idirghníomhú snáitheanna a bhainistiú.

Téarmaíocht

Próiseas - OS réad, spás seolta iargúlta, tá snáitheanna.
Snáithe - réad OS, an t-aonad forghníomhaithe is lú, cuid de phróiseas, roinneann snáitheanna cuimhne agus acmhainní eile eatarthu féin laistigh de phróiseas.
Multitasking - OS maoin, an cumas a reáchtáil próisis éagsúla ag an am céanna
Il-lárnach - airí de chuid an phróiseálaí, an cumas roinnt croíleacáin a úsáid chun sonraí a phróiseáil
Ilphróiseáil - maoin de chuid ríomhaire, an cumas oibriú go comhuaineach le roinnt próiseálaithe go fisiciúil
Ilsnáithe — airí de chuid próisis, an cumas próiseáil sonraí a dháileadh ar roinnt snáitheanna.
Comhthreomhaireacht - roinnt gníomhartha a dhéanamh go comhuaineach go fisiciúil in aghaidh an aonaid ama
Asincrónach — oibríocht a chur i gcrích gan fanacht leis an bpróiseáil seo a chur i gcrích; is féidir toradh an fhorghníomhaithe a phróiseáil níos déanaí.

Meafar

Níl gach sainmhíniú go maith agus tá míniú breise ag teastáil ó chuid acu, mar sin cuirfidh mé meafar faoi bhricfeasta cócaireachta leis an téarmaíocht a tugadh isteach go foirmiúil. Is próiseas é bricfeasta a chócaireacht sa mheafar seo.

Agus mé ag ullmhú bricfeasta ar maidin (LAP) Tagaim go dtí an chistin (Ríomhaire). Tá 2 lámh agam (Croíthe). Tá roinnt gléasanna sa chistin (IO): oigheann, citeal, tóstaer, cuisneoir. Casaim an gás air, cuirim friochadh air agus doirtim ola isteach ann gan fanacht le teas a chur air (asincrónach, Neamh-Blocáil-IO-Fan), Tógann mé na huibheacha as an gcuisneoir agus briseann mé iad i pláta, ansin builleann mé iad le lámh amháin (Snáithe #1), agus an dara (Snáithe #2) an pláta a choinneáil (Acmhainn Roinnte). Anois ba mhaith liom an citeal a chasadh air, ach níl mo dhóthain lámha agam (Starvation Snáithe) Le linn an ama seo, téitear an friochtán (An toradh a phróiseáil) agus doirtim an méid a bhuail mé isteach. Sroicheann mé go dtí an citeal agus cas air é agus go dúr féachaint ar an uisce a fhiuchadh ann (Blocáil-IO-Fan), cé go bhféadfadh sé i rith an ama seo an pláta a ní áit ar bhuail sé an omelet.

Chócaráil mé omelette ag baint úsáide as ach 2 lámh, agus níl níos mó agam, ach ag an am céanna, nuair a bhuailtí an omelette, rinneadh 3 oibríocht ag an am céanna: whipping an omelette, greim an pláta, téamh an friochtán. Is é an LAP an chuid is tapúla den ríomhaire, is é IO an rud is minice a mhoillíonn gach rud, mar sin is minic a réiteach éifeachtach é an LAP a áitiú le rud éigin agus sonraí á fháil ó IO.

Ag leanúint leis an meafar:

  • Más rud é agus mé i mbun omelet a ullmhú, dhéanfainn iarracht éadaí a athrú freisin, bheadh ​​​​sé seo mar shampla de multitasking. Nuance tábhachtach: tá ríomhairí i bhfad níos fearr ná daoine.
  • Cistin le roinnt príomhchócaire, mar shampla i mbialann - ríomhaire il-lárnach.
  • Go leor bialanna i gcúirt bia in ionad siopadóireachta - ionad sonraí

Uirlisí .NET

Tá .NET go maith ag obair le snáitheanna, mar atá le go leor rudaí eile. Le gach leagan nua, tugtar isteach níos mó agus níos mó uirlisí nua chun oibriú leo, sraitheanna nua astarraingthe thar snáitheanna OS. Agus iad ag obair le tógáil astarraingtí, úsáideann forbróirí creatlaí cur chuige a fhágann an deis, agus astarraingt ardleibhéil á n-úsáid acu, dul síos leibhéal amháin nó níos mó thíos. Is minic nach bhfuil sé seo riachtanach, go deimhin osclaíonn sé an doras chun tú féin a lámhach sa chos le gunna gráin, ach uaireanta, i gcásanna neamhchoitianta, b'fhéidir gurb é an t-aon bhealach chun fadhb a réiteach nach bhfuil réiteach ag an leibhéal astarraingthe reatha. .

De réir uirlisí, is éard atá i gceist agam comhéadain ríomhchláraithe feidhmchlár (API) a sholáthraíonn an creat agus na pacáistí tríú páirtí, chomh maith le réitigh bogearraí iomlána a shimplíonn an cuardach ar aon fhadhbanna a bhaineann le cód il-snáithithe.

Ag tosú snáithe

Is é an rang Snáithe an rang is bunúsaí i .NET chun oibriú le snáitheanna. Glacann an cruthaitheoir le duine de bheirt thoscaire:

  • ThreadStart - Gan paraiméadair
  • ParametrizedThreadStart - le paraiméadar amháin den chineál réad.

Déanfar an toscaire a fhorghníomhú sa snáithe nuachruthaithe tar éis an modh Tosaigh a ghlaoch. Má cuireadh toscaire den chineál ParametrizedThreadStart ar aghaidh chuig an cruthaitheoir, ní mór réad a chur ar aghaidh chuig an modh Tosaigh. Tá gá leis an meicníocht seo chun aon fhaisnéis áitiúil a aistriú chuig an sruth. Is fiú a thabhairt faoi deara gur oibríocht chostasach é snáithe a chruthú, agus gur rud trom é an snáithe féin, ar a laghad toisc go leithdháileann sé 1MB de chuimhne ar an gcruach agus go n-éilíonn sé idirghníomhú leis an OS API.

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

Léiríonn an rang ThreadPool an coincheap de linn snámha. In .NET, is píosa innealtóireachta é an linn snáithe, agus tá an-iarracht déanta ag na forbróirí ag Microsoft chun a chinntiú go n-oibríonn sé go barrmhaith i raon leathan cásanna.

Coincheap ginearálta:

Ón nóiméad a thosaíonn an t-iarratas, cruthaíonn sé roinnt snáitheanna sa chúlchiste sa chúlra agus soláthraíonn sé an cumas iad a ghlacadh lena n-úsáid. Má úsáidtear snáitheanna go minic agus i líon mór, leathnaíonn an linn snámha chun freastal ar riachtanais an té atá ag glaoch. Nuair nach bhfuil aon snáitheanna saor in aisce sa chomhthiomsú ag an am ceart, fanfaidh sé le ceann de na snáitheanna a thabhairt ar ais, nó cruthóidh sé ceann nua. Leanann sé go bhfuil an linn snáithe iontach do roinnt gníomhaíochtaí gearrthéarmacha agus droch-oiriúnach le haghaidh oibríochtaí a ritheann mar sheirbhísí ar fud oibriú iomlán an iarratais.

Chun snáithe ón linn a úsáid, tá modh QueueUserWorkItem ann a ghlacann le toscaire den chineál WaitCallback, a bhfuil an síniú céanna aige agus ParametrizedThreadStart, agus comhlíonann an paraiméadar a cuireadh ar aghaidh chuige an fheidhm chéanna.

ThreadPool.QueueUserWorkItem(...);

Úsáidtear an modh comhthiomsaithe snáithe is lú aithne RegisterWaitForSingleObject chun oibríochtaí IO neamh-blocála a eagrú. Glaofar ar an toscaire a aistrítear chuig an modh seo nuair a “Eisíodh” an WaitHandle.

ThreadPool.RegisterWaitForSingleObject(...)

Tá lasc ama snáithe ag .NET agus tá sé difriúil ó amadóirí WinForms/WPF sa mhéid is go nglaofar ar a láimhseálaí ar snáithe a tógadh ón linn.

System.Threading.Timer

Tá bealach sách coimhthíocha ann freisin chun toscaire a sheoladh lena fhorghníomhú chuig snáithe ón linn - an modh BeginInvoke.

DelegateInstance.BeginInvoke

Ba mhaith liom fanacht go hachomair ar an bhfeidhm ar féidir go leor de na modhanna thuas a ghlaoch uirthi - CreateThread ó Kernel32.dll Win32 API. Tá bealach ann, a bhuíochas le meicníocht na modhanna seachtracha, chun an fheidhm seo a ghlaoch. Ní fhaca mé a leithéid de ghlao ach uair amháin i sampla uafásach de chód oidhreachta, agus tá spreagadh an údair a rinne é seo go díreach fós ina rúndiamhair dom.

Kernel32.dll CreateThread

Amharc ar Shnáitheanna agus Dífhabhtaithe

Is féidir na snáitheanna cruthaithe agat, na comhpháirteanna tríú páirtí go léir, agus an linn .NET a fheiceáil i bhfuinneog Threads in Visual Studio. Ní thaispeánfaidh an fhuinneog seo faisnéis snáithe ach amháin nuair atá an feidhmchlár faoi dhífhabhtú agus i mód Sos. Anseo is féidir leat féachaint go caothúil ar ainmneacha cruachta agus ar thosaíochtaí gach snáithe, agus dífhabhtaithe a athrú go snáithe ar leith. Ag baint úsáide as an maoin Tosaíochta den rang Snáithe, is féidir leat an tosaíocht snáithe a shocrú, a bheidh an OC agus CLR bhrath mar mholadh nuair a roinnt am próiseálaí idir snáitheanna.

.NET: Uirlisí chun oibriú le multithreading agus asynchrony. Cuid 1

Leabharlann Comhuaineach Tasc

Tugadh isteach Leabharlann Comhthreomhar Tasc (TPL) i .NET 4.0. Anois is é an caighdeán agus an príomh-uirlis chun oibriú le asynchrony. Breathnaítear ar aon chód a úsáideann cur chuige níos sine mar oidhreacht. Is é bunaonad TPL an rang Tasc ón ainmspás System.Threading.Tasks. Is éard is tasc ann ná astarraingt thar snáithe. Leis an leagan nua den teanga C#, fuaireamar bealach galánta le bheith ag obair le Tasks - sioncronaigh/fanacht le hoibreoirí. D'fhág na coincheapa seo gur féidir cód asincrónach a scríobh amhail is go raibh sé simplí agus sioncronach, d'fhág sé seo gur féidir fiú do dhaoine ar bheagán tuisceana ar oibriú inmheánach snáitheanna feidhmchláir a scríobh a úsáideann iad, feidhmchláir nach reoiteann agus iad ag déanamh oibríochtaí fada. Is ábhar é async / await a úsáid le haghaidh alt amháin nó fiú roinnt alt, ach déanfaidh mé iarracht an brí a bhaint as i gcúpla abairt:

  • Is modhnóir é async ar mhodh a thugann Tasc ar ais nó ar neamhní
  • agus is oibreoir feithimh Tasc neamh-blocála é fanacht.

Arís eile: déanfaidh an t-oibreoir fanacht, sa chás ginearálta (tá eisceachtaí), an snáithe forghníomhaithe reatha a scaoileadh a thuilleadh, agus nuair a chríochnaíonn an Tasc a fhorghníomhú, agus an snáithe (go deimhin, bheadh ​​​​sé níos ceart an comhthéacs a rá. , ach níos mó faoi sin níos déanaí) leanúint ar aghaidh leis an modh a fhorghníomhú tuilleadh. Taobh istigh de .NET, cuirtear an mheicníocht seo i bhfeidhm ar an mbealach céanna le tuairisceán toraidh, nuair a bhíonn an modh scríofa ina rang iomlán, is meaisín stáit é agus is féidir é a fhorghníomhú i bpíosaí ar leith ag brath ar na stáit seo. Is féidir le haon duine a bhfuil suim aige/aici aon chód simplí a scríobh ag baint úsáide as asynс/fanacht, an tionól a thiomsú agus a fheiceáil ag baint úsáide as JetBrains dotPeek le Cód Ginte Tiomsaitheora cumasaithe.

Breathnaímid ar roghanna maidir le Tasc a sheoladh agus a úsáid. Sa chódshampla thíos, cruthaímid tasc nua nach ndéanann aon rud úsáideach (Snáithe.Sleep(10000)), ach sa saol fíor ba chóir go mbeadh sé seo roinnt oibre casta LAP-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
}

Cruthaítear Tasc le roinnt roghanna:

  • Is éard atá i gceist le LongRunning ná nach gcuirfear an tasc i gcrích go tapa, rud a chiallaíonn go bhféadfadh sé gur fiú smaoineamh gan snáithe a thógáil ón linn, ach ceann ar leith a chruthú don Tasc seo ionas nach ndéanfaidh sé dochar do dhaoine eile.
  • AttachedToParent - Is féidir tascanna a shocrú in ordlathas. Dá mbainfí úsáid as an rogha seo, d’fhéadfadh go mbeadh an Tasc i riocht ina bhfuil sé féin críochnaithe agus ag fanacht lena leanaí a chur i gcrích.
  • PreferFairness - ciallaíonn sé sin go mbeadh sé níos fearr Tascanna a cuireadh le cur i gcrích níos luaithe a fheidhmiú roimh iad siúd a sheoltar níos déanaí. Ach níl anseo ach moladh agus ní ráthaítear torthaí.

Is é CancellationToken an dara paraiméadar a cuireadh ar aghaidh chuig an modh. Chun cealú oibríochta a láimhseáil i gceart tar éis dó tosú, ní mór an cód atá á fhorghníomhú a líonadh le seiceálacha don stát CancellationToken. Mura bhfuil aon seiceálacha ann, ansin beidh an modh Cealaigh ar a dtugtar ar an réad CancellationTokenSource in ann stop a chur le cur i gcrích an Tasc ach amháin sula dtosaíonn sé.

Is réad sceidealóra den chineál TaskScheduler é an paraiméadar deiridh. Tá an rang seo agus a sliocht deartha chun straitéisí a rialú chun Tascanna a dháileadh thar snáitheanna; de réir réamhshocraithe, déanfar an Tasc ar snáithe randamach ón linn.

Cuirtear an t-oibreoir fanacht i bhfeidhm ar an Tasc cruthaithe, rud a chiallaíonn go ndéanfar an cód a scríobhtar ina dhiaidh, má tá ceann ann, a fhorghníomhú sa chomhthéacs céanna (go minic ciallaíonn sé seo ar an snáithe céanna) leis an gcód roimh fanacht.

Tá an modh marcáilte mar neamhní async, rud a chiallaíonn gur féidir leis an oibreoir feithimh a úsáid, ach ní bheidh an cód glaonna in ann fanacht lena fhorghníomhú. Más gá gné den sórt sin, ansin caithfidh an modh Tasc a thabhairt ar ais. Tá modhanna marcáilte ar neamhní async coitianta go leor: mar riail, is iad seo láimhseálaithe imeachtaí nó modhanna eile a oibríonn ar an bprionsabal tine agus dearmad. Más gá duit ní hamháin an deis fanacht go dtí deireadh an fhorghníomhaithe a thabhairt, ach freisin an toradh a thabhairt ar ais, ansin is gá duit Tasc a úsáid.

Ar an Tasc a d'fhill an modh StartNew, chomh maith le haon cheann eile, is féidir leat an modh ConfigureAwait a ghlaoch leis an bparaiméadar bréagach, ansin leanfar le forghníomhú tar éis fanacht ní ar an gcomhthéacs a gabhadh, ach ar cheann treallach. Ba cheart é seo a dhéanamh i gcónaí nuair nach bhfuil an comhthéacs forghníomhaithe tábhachtach don chód tar éis fanacht. Is moladh é seo freisin ó na Ballstáit agus cód á scríobh a sheachadfar i leabharlann.

Déanaimis cuimhneamh ar an gcaoi ar féidir leat fanacht le Tasc a chríochnú. Anseo thíos tá sampla de chód, le tráchtanna maidir le cathain a dhéantar an t-ionchas go coinníollach go maith agus nuair a dhéantar é go coinníollach go 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
}

Sa chéad shampla, táimid ag fanacht leis an Tasc a chur i gcrích gan bac a chur ar an snáithe glaonna; ní fhillfimid ar an toradh a phróiseáil ach amháin nuair a bhíonn sé ann cheana féin; go dtí sin, fágtar an snáithe glaonna dá ghléasanna féin.

Sa dara rogha, cuirimid bac ar an snáithe glaonna go dtí go ríomhtar toradh an mhodha. Tá sé seo go dona, ní hamháin toisc go bhfuil snáithe áitithe againn, acmhainn chomh luachmhar den chlár, le díomhaoineas simplí, ach freisin mar má tá cód an mhodha a dtugaimid air ag fanacht, agus éilíonn an comhthéacs sioncronaithe filleadh ar an snáithe glaonna ina dhiaidh sin. fanacht, ansin gheobhaidh muid stop: Fanann an snáithe glaonna chun toradh an mhodha asincrónach a ríomh, déanann an modh asincrónach iarracht gan staonadh chun leanúint lena fhorghníomhú sa snáithe glaonna.

Míbhuntáiste eile a bhaineann leis an gcur chuige seo is ea láimhseáil earráidí casta. Is é fírinne an scéil go bhfuil earráidí sa chód asincrónach nuair a úsáidtear async / fanacht an-éasca a láimhseáil - iad féin a iompar mar an gcéanna dá mbeadh an cód sioncronach. Cé go gcuirfimid deamhandacht feithimh shioncronach i bhfeidhm ar Thasc, is eisceacht comhiomlán é an buneisceacht, i.e. Chun an eisceacht a láimhseáil, beidh ort an cineál InnerException a scrúdú agus má shlabhra tú féin a scríobh taobh istigh de bhloc gabhála amháin nó an ghabháil a úsáid nuair a bhíonn tú á tógáil, in ionad an tslabhra bloic ghabhála atá níos eolach sa domhan C#.

Tá an tríú sampla agus an ceann deiridh marcáilte freisin go dona ar an gcúis chéanna agus tá na fadhbanna céanna go léir iontu.

Tá na modhanna WhenAny and WhenAll thar a bheith áisiúil chun fanacht le grúpa Tascanna; fillteann siad grúpa Tascanna isteach i gceann amháin, rud a scaoilfidh nuair a chuirtear Tasc ón ngrúpa i bhfeidhm ar dtús, nó nuair a bheidh a gcur i gcrích críochnaithe acu go léir.

Snáitheanna a stopadh

Ar chúiseanna éagsúla, b'fhéidir go mbeadh sé riachtanach an sreabhadh a stopadh tar éis dó tosú. Tá roinnt bealaí ann chun é seo a dhéanamh. Tá dhá mhodh ainmnithe go cuí ag an rang Snáithe: Ginmhilleadh и Cur isteach. Ní mholtar go mór an chéad cheann lena n-úsáid, mar gheall ar tar éis é a ghlaoch ag aon nóiméad randamach, le linn aon teagasc a phróiseáil, déanfar eisceacht a chaitheamh ThreadAbortedException. Ní bhíonn tú ag súil go gcaitear a leithéid d’eisceacht agus tú ag méadú aon athróg slánuimhir, ceart? Agus nuair a bhíonn an modh seo á úsáid, is staid an-dáiríre é seo. Más gá duit an CLR a chosc ó eisceacht den sórt sin a ghiniúint i gcuid áirithe den chód, is féidir leat é a fhilleadh i nglaonna Snáithe.BeginCriticalRegion, Thread.EndCriticalRegion. Tá aon chód scríofa i mbloc deiridh fillte i nglaonna den sórt sin. Ar an gcúis seo, i dhoimhneas an chreatchóid is féidir leat bloic a aimsiú le hiarracht folamh, ach ní folamh ar deireadh. Ní spreagann Microsoft an modh seo chomh mór sin nár chuimsigh siad é i gcroílár .net.

Oibríonn an modh Idirbhriseadh ar bhealach níos intuartha. Is féidir leis an snáithe a bhriseadh ach amháin ThreadInterruptedEisceacht ach amháin le linn na chuimhneacháin sin nuair a bhíonn an snáithe i stát feithimh. Téann sé isteach sa stát seo agus é ag crochadh agus é ag fanacht le WaitHandle, glas, nó tar éis glaoch ar Thread.Sleep.

Tá an dá rogha a bhfuil cur síos orthu thuas dona mar gheall ar a neamh-intuarthacht. Is é an réiteach ná struchtúr a úsáid CancellationToken agus rang CancellationTokenSource. Is é seo an pointe: cruthaítear sampla den rang CancellationTokenSource agus ní féidir ach an té ar leis é an oibríocht a stopadh trí ghlao a chur ar an modh Cealaigh. Ní thugtar ach an CancellationToken chuig an oibríocht féin. Ní féidir le húinéirí CancellationToken an oibríocht a chur ar ceal iad féin, ach ní féidir leo a sheiceáil ach an bhfuil an oibríocht curtha ar ceal. Tá maoin Boole ann chuige seo IsCancellationRequested agus modh Iarrtha ThrowIfCancel. Beidh an dara ceann caith eisceacht TaskCancelledException dá nglaodh an modh Cealaigh ar an ásc CancellationToken a bheith parroctha. Agus is é seo an modh a molaim úsáid a bhaint as. Is feabhas é seo ar na roghanna roimhe seo trí smacht iomlán a fháil ar an bpointe inar féidir deireadh a chur le hoibríocht eisceachtúil.

Is é an rogha is brúidiúla chun snáithe a stopadh ná feidhm TermminateThread Win32 API a ghlaoch. D’fhéadfadh iompar an CLR tar éis an fheidhm seo a ghlaoch a bheith dothuartha. Ar MSDN tá an méid seo a leanas scríofa faoin bhfeidhm seo: “Is feidhm chontúirteach é TermminateThread nár cheart a úsáid ach amháin sna cásanna is foircneacha. “

API oidhreachta a thiontú go Tasc Bunaithe ag baint úsáide as modh FromAsync

Má tá tú t-ádh go leor a bheith ag obair ar thionscadal a cuireadh tús leis tar éis Tascanna a thabhairt isteach agus scortha de bheith ina chúis le uafás ciúin don chuid is mó d’fhorbróirí, ansin ní bheidh ort déileáil le go leor API d’aois, idir chinn tríú páirtí agus iad siúd d’fhoireann. tá céasadh san am atá caite. Ar ámharaí an tsaoil, thug foireann .NET Framework aire dúinn, cé go mb’fhéidir gurbh é an sprioc aire a thabhairt dúinn féin. Bíodh sin mar atá, tá roinnt uirlisí ag .NET chun cód a thiontú gan phian atá scríofa i sean-chur chuige ríomhchlárúcháin asincrónach chuig an gceann nua. Is é ceann acu an modh FromAsync de TaskFactory. Sa chódshampla thíos, fillteaim na sean-mhodhanna async sa rang WebRequest i dTasc ag baint úsáide as an modh seo.

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

Níl anseo ach sampla agus ní dócha go mbeidh ort é seo a dhéanamh le cineálacha ionsuite, ach níl aon seantionscadal ach ag cur thar maoil le modhanna BeginDoSomething a thugann modhanna IAsyncResult agus EndDoSomething a fhaigheann é ar ais.

Tiontaigh API oidhreachta go Tasc Bunaithe ag baint úsáide as rang TaskCompletionSource

Uirlis thábhachtach eile le breithniú ná an rang TaskCompletionSource. Maidir le feidhmeanna, cuspóir agus prionsabal na hoibríochta, d'fhéadfadh sé a bheith beagán i gcuimhne ar an modh RegisterWaitForSingleObject den rang ThreadPool, ar scríobh mé faoi thuas. Ag baint úsáide as an rang seo, is féidir leat sean-API asincrónacha a fhilleadh go héasca agus go háisiúil i dTascanna.

Déarfaidh tú gur labhair mé cheana faoin modh FromAsync den rang TaskFactory atá beartaithe chun na gcríoch seo. Anseo beidh orainn cuimhneamh ar stair iomlán fhorbairt na samhlacha asincrónacha i .net a thairg Microsoft le 15 bliana anuas: roimh an Patrún Asincrónach Tascbhunaithe (TAP), bhí an Patrún Ríomhchláraithe Asincrónach (APP), a a bhí faoi mhodhanna TosaighDéan Rud éigin ag filleadh Toradh IAsync agus modhanna deireadhDoSomething a ghlacann leis agus ar mhaithe le hoidhreacht na mblianta seo tá modh FromAsync foirfe, ach le himeacht ama, cuireadh an Patrún Asincrónach Bunaithe ar Imeachtaí ina ionad (AGUS AP), a ghlac leis go n-ardófaí teagmhas nuair a cuireadh an oibríocht asincrónach i gcrích.

Tá TaskCompletionSource foirfe chun Tascanna agus APIanna oidhreachta a fhilleadh bunaithe ar mhúnla an imeachta. Is é seo a leanas croílár a chuid oibre: tá maoin phoiblí de chineál Tasc ag réad den aicme seo, ar féidir a staid a rialú trí mhodhanna SetResult, SetException, etc. den rang TaskCompletionSource. In áiteanna inar cuireadh an t-oibreoir feithimh i bhfeidhm ar an Tasc seo, déanfar é a fhorghníomhú nó teipfear air ach amháin ag brath ar an modh a cuireadh i bhfeidhm ar an Tasc Críochnaithe. Mura bhfuil sé soiléir go fóill, déanaimis féachaint ar an sampla cód seo, áit a bhfuil roinnt sean-EAP API fillte i dTasc ag baint úsáide as Tascanna Comhlánaithe: nuair a lasann an teagmhas, aistreofar an Tasc chuig an stát Críochnaithe, agus an modh a chuir an t-oibreoir fanacht i bhfeidhm chun an Tasc seo a chur i gcrích arís tar éis an réad a fháil mar thoradh ar.

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

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

    result completionSource.Task;
}

Leideanna & Seifteanna TaskCompletionSource

Ní féidir sean-API a fhilleadh agus TaskCompletionSource a úsáid. Trí úsáid a bhaint as an rang seo tá féidearthacht spéisiúil ann chun APIanna éagsúla a dhearadh ar Thascanna nach n-úsáidtear snáitheanna. Agus is acmhainn daor é an sruth, mar a chuimhnímid, agus tá a líon teoranta (go príomha ag an méid RAM). Is féidir an teorannú seo a bhaint amach go héasca, mar shampla, feidhmchlár gréasáin lódáilte le loighic ghnó casta a fhorbairt. Déanaimis machnamh ar na féidearthachtaí a bhfuilim ag caint fúthu agus a leithéid de chleas mar Long-vótaíocht á chur i bhfeidhm.

I mbeagán focal, is é croílár an cleas seo: ní mór duit faisnéis a fháil ón API faoi roinnt imeachtaí a tharlaíonn ar a thaobh, agus ní féidir leis an API, ar chúis éigin, an t-imeacht a thuairisciú, ach ní féidir leis ach an stát a thabhairt ar ais. Sampla díobh seo is ea gach API a tógadh ar bharr HTTP roimh amanna WebSocket nó nuair a bhí sé dodhéanta ar chúis éigin an teicneolaíocht seo a úsáid. Is féidir leis an gcliant a iarraidh ar an bhfreastalaí HTTP. Ní féidir leis an bhfreastalaí HTTP féin cumarsáid a thionscnamh leis an gcliant. Is é réiteach simplí ná an freastalaí a vótaíocht ag baint úsáide as lasc ama, ach cruthaíonn sé seo ualach breise ar an bhfreastalaí agus moill bhreise ar an meán TimerInterval / 2. Chun dul timpeall air seo, ceapadh cleas ar a dtugtar Vótaíocht Fada, rud a chuireann moill ar an bhfreagra ó an freastalaí go dtí go rachaidh an Teorainn Ama in éag nó go dtarlóidh teagmhas. Má tharla an teagmhas, déantar é a phróiseáil, mura bhfuil, seoltar an t-iarratas arís.

while(!eventOccures && !timeoutExceeded)  {

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

Ach beidh réiteach den sórt sin uafásach chomh luath agus a mhéadaíonn líon na gcliant atá ag fanacht leis an imeacht, mar gheall ar ... Tá snáithe iomlán ag gach cliant den sórt sin ag fanacht le himeacht. Sea, agus faigheann muid moill 1ms breise nuair a spreagtar an t-imeacht, is minic nach bhfuil sé seo suntasach, ach cén fáth na bogearraí a dhéanamh níos measa ná mar is féidir? Má bhainimid Thread.Sleep(1), ansin go neamhbhalbh cuirfimid croí próiseálaí amháin 100% díomhaoin, ag rothlú i dtimthriall gan úsáid. Ag baint úsáide as TaskCompletionSource is féidir leat an cód seo a athdhéanamh go héasca agus na fadhbanna go léir thuas a réiteach:

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);
    }
}

Níl an cód seo réidh le haghaidh táirgeadh, ach leagan taispeána. Chun é a úsáid i gcásanna fíor, ní mór duit freisin, ar a laghad, an cás a láimhseáil nuair a thagann teachtaireacht ag am nach bhfuil aon duine ag súil leis: sa chás seo, ba cheart don mhodh AsseptMessageAsync Tasc atá críochnaithe cheana féin a chur ar ais. Más é seo an cás is coitianta, ansin is féidir leat smaoineamh ar ValueTask a úsáid.

Nuair a fhaighimid iarratas ar theachtaireacht, cruthaímid agus cuirimid TaskCompletionSource san fhoclóir, agus ansin fanaimid lena dtarlaíonn ar dtús: téann an t-eatramh ama sonraithe in éag nó faightear teachtaireacht.

ValueTask: cén fáth agus conas

Gineann na hoibreoirí async / fanacht, cosúil leis an oibreoir tuairisceáin toraidh, meaisín stáit ón modh, agus is é seo a chruthaíonn réad nua, nach bhfuil beagnach i gcónaí tábhachtach, ach i gcásanna neamhchoitianta is féidir leis fadhb a chruthú. D'fhéadfadh an cás seo a bheith ina mhodh ar a dtugtar i ndáiríre go minic, táimid ag caint faoi na mílte agus na mílte glaonna in aghaidh an tsoicind. Má scríobhtar modh den sórt sin sa chaoi is go dtugann sé ar ais toradh i bhformhór na gcásanna ag seachaint na modhanna feithimh go léir, ansin soláthraíonn .NET uirlis chun é seo a bharrfheabhsú - struchtúr ValueTask. Chun é a dhéanamh soiléir, breathnaímid ar shampla dá úsáid: tá taisce ann a théimid go minic. Tá roinnt luachanna ann agus ansin déanaimid ar ais go simplí iad; mura bhfuil, téann muid chuig IO mall chun iad a fháil. Ba mhaith liom an dara ceann a dhéanamh go asincrónach, rud a chiallaíonn go mbeidh an modh iomlán asincrónach. Mar sin, is é seo a leanas an bealach soiléir chun an modh a scríobh:

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

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

Mar gheall ar an fonn leas iomlán a bhaint as beagán, agus eagla beag ar cad a ghinfidh Roslyn agus an cód seo á thiomsú agat, is féidir leat an sampla seo a athscríobh mar seo a leanas:

public Task<string> GetById(int id) {

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

Go deimhin, is é an réiteach is fearr sa chás seo ná an cosán te a bharrfheabhsú, is é sin, luach a fháil ón bhfoclóir gan aon leithdháiltí agus ualach neamhriachtanach ar an GC, agus sna cásanna neamhchoitianta sin nuair is gá dúinn fós dul chuig IO le haghaidh sonraí. , fanfaidh gach rud mar móide/lúide ar an seanbhealach:

public ValueTask<string> GetById(int id) {

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

Breathnaímis níos dlúithe ar an bpíosa cód seo: má tá luach sa taisce, cruthaímid struchtúr, ar shlí eile beidh an tasc fíor fillte i gceann brí. Is cuma leis an gcód glaonna cén cosán ar cuireadh an cód seo i gcrích i: Beidh ValueTask, ó thaobh comhréire C# de, mar an gcéanna le Tasc rialta sa chás seo.

TaskSchedulers: straitéisí tasc-lainseála a bhainistiú

Is é an chéad API eile ba mhaith liom a mheas ná an rang TaskScheduler agus a díorthaigh. Luaigh mé cheana thuas go bhfuil an cumas ag TPL straitéisí a bhainistiú chun Tascanna a dháileadh thar snáitheanna. Sainmhínítear straitéisí den sórt sin i sliocht an ranga TaskScheduler. Is féidir beagnach aon straitéis a d’fhéadfadh a bheith uait a fháil sa leabharlann. Eisínteachtaí Comhuaineach, arna fhorbairt ag Microsoft, ach níl sé ina chuid de .NET, ach arna sholáthar mar phacáiste Nuget. Breathnaímid go hachomair ar chuid acu:

  • CurrentThreadTaskScheduler — déanann sé Tascanna ar an snáithe reatha
  • LimitedConcurrencyLevelTaskScheduler — cuireann sé teorainn le líon na dTascanna a dhéantar go comhuaineach faoi pharaiméadar N, a nglactar leis sa chruthaitheoir
  • OrdaíodhTaskScheduler — a shainmhínítear mar LimitedConcurrencyLevelTaskScheduler(1), mar sin déanfar tascanna a chur i gcrích go seicheamhach.
  • WorkStealingTaskScheduler - uirlisí obair-goid cur chuige maidir le dáileadh tascanna. Go bunúsach is ThreadPool ar leith é. Réitíonn sé an fhadhb gur rang statach é NET ThreadPool, ceann do gach feidhmchlár, rud a chiallaíonn go bhféadfadh fo-iarmhairtí a bheith mar thoradh ar ró-ualú nó úsáid mhícheart i gcuid amháin den chlár i gcuid eile. Thairis sin, tá sé thar a bheith deacair cúis lochtanna den sórt sin a thuiscint. Sin. D’fhéadfadh go mbeadh gá le WorkStealingTaskSchedulers ar leith a úsáid i gcodanna den chlár ina bhféadfadh úsáid ThreadPool a bheith ionsaitheach agus nach féidir a thuar.
  • QueuedTaskScheduler — ligeann sé duit tascanna a dhéanamh de réir rialacha tosaíochta na scuaine
  • ThreadPerTaskScheduler — cruthaítear snáithe ar leith do gach Tasc a dhéantar air. D’fhéadfadh sé a bheith úsáideach le haghaidh tascanna a thógann sé tamall fada a thuar le cur i gcrích.

Tá mionsonraithe maith airteagal faoi ​​TaskSchedulers ar bhlag microsoft.

Le haghaidh dífhabhtaithe áisiúil ar gach rud a bhaineann le Tascanna, tá fuinneog Tascanna ag Visual Studio. Sa fhuinneog seo is féidir leat staid reatha an taisc a fheiceáil agus léim chuig an líne chóid atá á fheidhmiú faoi láthair.

.NET: Uirlisí chun oibriú le multithreading agus asynchrony. Cuid 1

PLinq agus an rang Comhthreomhar

Chomh maith le Tascanna agus gach rud a dúradh fúthu, tá dhá uirlis níos suimiúla i .NET: PLinq (Linq2Parallel) agus an rang Parallel. Geallann an chéad cheann go ndéanfar gach oibríocht Linq go comhthreomhar ar shnáitheanna iolracha. Is féidir líon na snáitheanna a chumrú trí úsáid a bhaint as modh sínte WithDegreeOfParallelism. Ar an drochuair, is minic nach mbíonn go leor faisnéise ag PLinq ina mhodh réamhshocraithe faoi inmheánaigh do fhoinse sonraí chun gnóthachan luais suntasach a sholáthar, ar an láimh eile, tá costas na hiarrachta an-íseal: ní gá duit ach glaoch ar an modh AsParallel roimhe seo. slabhra na modhanna Linq agus tástálacha feidhmíochta a reáchtáil. Ina theannta sin, is féidir faisnéis bhreise a chur ar aghaidh chuig PLinq faoi nádúr do fhoinse sonraí ag baint úsáide as meicníocht Deighiltí. Is féidir leat níos mó a léamh anseo и anseo.

Soláthraíonn an rang statach Comhthreomhar modhanna chun atriall a dhéanamh trí bhailiúchán Foreach go comhthreomhar, lúb For a fhorghníomhú, agus toscairí iolracha a fhorghníomhú in Invoke comhthreomhar. Cuirfear stop le forghníomhú an tsnáithe reatha go dtí go mbeidh na ríomhanna críochnaithe. Is féidir líon na snáitheanna a chumrú trí ParallelOptions a rith mar an argóint dheireanach. Is féidir leat TaskScheduler agus CancellationToken a shonrú freisin ag baint úsáide as roghanna.

Torthaí

Nuair a thosaigh mé ag scríobh an ailt seo bunaithe ar ábhair mo thuarascáil agus ar an eolas a bhailigh mé le linn mo chuid oibre ina dhiaidh, ní raibh mé ag súil go mbeadh an oiread sin de ann. Anois, nuair a insíonn an t-eagarthóir téacs ina bhfuil an t-alt seo á chlóscríobh agam go doiléir dom go bhfuil leathanach 15 imithe, déanfaidh mé achoimre ar na torthaí eatramhacha. Clúdófar cleasanna eile, APIanna, uirlisí amhairc agus gaistí sa chéad alt eile.

Conclúidí:

  • Ní mór duit a bheith eolach ar na huirlisí chun oibriú le snáitheanna, asincrony agus comhthreomharachas chun acmhainní ríomhairí pearsanta nua-aimseartha a úsáid.
  • Tá go leor uirlisí éagsúla ag NET chun na críocha seo
  • Ní raibh gach ceann acu le feiceáil ag an am céanna, mar sin is minic gur féidir leat cinn oidhreachta a fháil, áfach, tá bealaí ann chun sean-API a thiontú gan mórán iarracht.
  • Léiríonn na ranganna Thread and ThreadPool oibriú le snáitheanna i .NET
  • Tá na modhanna Thread.Abort, Thread.Interrupt, agus Win32 API TermminateThread contúirteach agus ní mholtar iad le húsáid. Ina áit sin, tá sé níos fearr meicníocht CancellationToken a úsáid
  • Is acmhainn luachmhar é sreabhadh agus tá a soláthar teoranta. Ba cheart cásanna ina bhfuil snáitheanna gnóthach ag fanacht le himeachtaí a sheachaint. Chuige seo tá sé áisiúil an rang TaskCompletionSource a úsáid
  • Is iad na huirlisí .NET is cumhachtaí agus is forásaí chun oibriú le comhthreomhaireacht agus asincrony Tascanna.
  • Cuireann na hoibreoirí c# async/await an coincheap maidir le feithimh gan bac a chur i bhfeidhm
  • Is féidir leat dáileadh Tascanna thar snáitheanna a rialú trí úsáid a bhaint as ranganna atá díorthaithe ó TaskScheduler
  • Is féidir leis an struchtúr ValueTask a bheith úsáideach chun teochosáin agus trácht cuimhne a bharrfheabhsú
  • Soláthraíonn fuinneoga Tascanna agus Snáitheanna Visual Studio go leor faisnéise úsáideach chun cód il-snáithe nó asincrónach a dhífhabhtú
  • Is uirlis fhionnuar é PLinq, ach b'fhéidir nach bhfuil go leor faisnéise aige faoi d'fhoinse sonraí, ach is féidir é seo a shocrú ag baint úsáide as an meicníocht deighilte
  • Le leanúint ...

Foinse: will.com

Add a comment