.NET: Għodda biex taħdem b'multithreading u asinkronija. Parti 1

Qed nippubblika l-artiklu oriġinali fuq Habr, li t-traduzzjoni tiegħu hija mwaħħla fil-korporattiva post blog.

Il-ħtieġa li tagħmel xi ħaġa b'mod mhux sinkroniku, mingħajr ma tistenna r-riżultat hawn u issa, jew li jaqsam xogħol kbir fost diversi unitajiet li jwettquha, kienet teżisti qabel il-miġja tal-kompjuters. Bil-miġja tagħhom, din il-ħtieġa saret tanġibbli ħafna. Issa, fl-2019, qed nittajpja dan l-artikolu fuq laptop bi proċessur Intel Core ta '8-core, li fuqu qed jaħdmu aktar minn mitt proċess b'mod parallel, u saħansitra aktar ħjut. Fil-qrib, hemm telefon kemmxejn shabby, mixtri ftit snin ilu, għandu proċessur 8-core abbord. Ir-riżorsi tematiċi huma mimlija artikli u vidjows fejn l-awturi tagħhom jammiraw l-ismartphones ewlenin ta’ din is-sena li fihom proċessuri b’16-il qalba. MS Azure jipprovdi magna virtwali bi proċessur ċentrali 20 u 128 TB RAM għal inqas minn $2/siegħa. Sfortunatament, huwa impossibbli li jiġi estratt il-massimu u jiġi sfruttat din il-qawwa mingħajr ma tkun tista 'timmaniġġja l-interazzjoni tal-ħjut.

Terminoloġija

Proċess - Oġġett OS, spazju ta 'indirizz iżolat, fih ħjut.
Thread - oġġett OS, l-iżgħar unità ta 'eżekuzzjoni, parti minn proċess, ħjut jaqsmu memorja u riżorsi oħra bejniethom fi ħdan proċess.
Xandir b'ħafna - Proprjetà OS, il-kapaċità li tmexxi diversi proċessi simultanjament
Multi-qalba - proprjetà tal-proċessur, il-kapaċità li tuża diversi cores għall-ipproċessar tad-dejta
Multiproċessar - proprjetà ta 'kompjuter, il-kapaċità li simultanjament taħdem ma' diversi proċessuri fiżikament
Multithreading — proprjetà ta' proċess, l-abbiltà li jqassam l-ipproċessar tad-dejta fost diversi threads.
Paralleliżmu - it-twettiq ta' diversi azzjonijiet fiżikament simultanjament għal kull unità ta' ħin
Asinkronija — eżekuzzjoni ta' operazzjoni mingħajr ma tistenna t-tlestija ta' dan l-ipproċessar; ir-riżultat ta' l-eżekuzzjoni jista' jiġi pproċessat aktar tard.

Metafora

Mhux id-definizzjonijiet kollha huma tajbin u xi wħud jeħtieġu spjegazzjoni addizzjonali, għalhekk ser inżid metafora dwar it-tisjir tal-kolazzjon mat-terminoloġija introdotta formalment. It-tisjir tal-kolazzjon f'din il-metafora huwa proċess.

Waqt li nipprepara l-kolazzjon filgħodu jien (CPU) niġi fil-kċina (Kompjuter). Għandi 2 idejn (Cores). Hemm numru ta' apparati fil-kċina (IO): forn, kitla, toaster, friġġ. Nixgħel il-gass, poġġi taġen fuqha u ferra’ żejt fih mingħajr ma nistenna li jisħon (b'mod mhux sinkroniku, Non-Blocking-IO-Wait), nieħu l-bajd mill-friġġ u nkissirhom fi platt, imbagħad ħabbathom b'id waħda (Thread#1), u t-tieni (Thread#2) li żżomm il-pjanċa (Shared Resource). Issa nixtieq nixgħel il-kitla, imma m'għandix biżżejjed idejn (Ġuħ Thread) Matul dan iż-żmien, il-qali jisħon (Ipproċessa r-riżultat) li fih ferragħ dak li ħallejt bit-tarjola. Nilħaq il-kitla u nixgħelha u stupidament nara l-ilma jagħli fiha (Imblukkar-IO-Stenna), għalkemm f’dan iż-żmien seta’ ħasel il-platt fejn ħabbat l-omelet.

Sajjt omelette bl-użu ta' 2 idejn biss, u m'għandix aktar, iżda fl-istess ħin, fil-mument tat-tarjola tal-omelette, saru 3 operazzjonijiet f'daqqa: tħawwad l-omelette, iżżomm il-platt, saħħan il-qali Is-CPU huwa l-iktar parti mgħaġġla tal-kompjuter, IO huwa dak li ħafna drabi kollox imewwet, għalhekk ħafna drabi soluzzjoni effettiva hija li tokkupa s-CPU b'xi ħaġa waqt li tirċievi dejta minn IO.

Tkompli l-metafora:

  • Jekk fil-proċess ta 'preparazzjoni ta' omelet, nipprova wkoll nibdel il-ħwejjeġ, dan ikun eżempju ta 'multitasking. Sfumatura importanti: il-kompjuters huma ħafna aħjar f'dan min-nies.
  • Kċina b'diversi koki, pereżempju f'ristorant - kompjuter multi-core.
  • Ħafna ristoranti fi food court f'ċentru tax-xiri - data center

Għodda .NET

.NET huwa tajjeb biex jaħdem ma 'ħjut, bħal ma' ħafna affarijiet oħra. Ma 'kull verżjoni ġdida, tintroduċi aktar u aktar għodod ġodda biex taħdem magħhom, saffi ġodda ta' astrazzjoni fuq ħjut tal-OS. Meta jaħdmu mal-kostruzzjoni ta 'estrazzjonijiet, l-iżviluppaturi tal-qafas jużaw approċċ li jħalli l-opportunità, meta tuża estrazzjoni ta' livell għoli, li jinżlu livell wieħed jew aktar taħt. Ħafna drabi dan mhux meħtieġ, fil-fatt jiftaħ il-bieb biex tispara lilek innifsek fis-sieq b'shotgun, iżda kultant, f'każijiet rari, jista 'jkun l-uniku mod biex issolvi problema li mhix solvuta fil-livell attwali ta' astrazzjoni .

B'għodod, infisser kemm interfaces tal-ipprogrammar tal-applikazzjoni (APIs) ipprovduti mill-qafas u pakketti ta 'partijiet terzi, kif ukoll soluzzjonijiet ta' softwer sħaħ li jissimplifikaw it-tfittxija għal kwalunkwe problema relatata mal-kodiċi b'ħafna kamini.

Jibda ħajta

Il-klassi Thread hija l-aktar klassi bażika f'.NET biex taħdem bil-ħjut. Il-kostruttur jaċċetta wieħed minn żewġ delegati:

  • ThreadStart — L-ebda parametri
  • ParametrizedThreadStart - b'parametru wieħed ta' oġġett tat-tip.

Id-delegat se jiġi eżegwit fil-ħajt maħluq ġdid wara li jsejjaħ il-metodu Start.Jekk delegat tat-tip ParametrizedThreadStart ġie mgħoddi lill-kostruttur, allura oġġett għandu jiġi mgħoddi lill-metodu Start. Dan il-mekkaniżmu huwa meħtieġ biex tittrasferixxi kwalunkwe informazzjoni lokali għall-fluss. Ta 'min jinnota li l-ħolqien ta' ħajta hija operazzjoni għalja, u l-ħajt innifsu huwa oġġett tqil, għall-inqas minħabba li jalloka 1MB ta 'memorja fuq il-munzell u jeħtieġ interazzjoni mal-API tal-OS.

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

Il-klassi ThreadPool tirrappreżenta l-kunċett ta 'pool. F'.NET, il-grupp tal-ħajt huwa biċċa ta 'inġinerija, u l-iżviluppaturi ta' Microsoft għamlu ħafna sforz biex jiżguraw li taħdem bl-aħjar mod f'varjetà wiesgħa ta 'xenarji.

Kunċett ġenerali:

Mill-mument li tibda l-applikazzjoni, toħloq diversi ħjut fir-riżerva fl-isfond u tipprovdi l-abbiltà li teħodhom għall-użu. Jekk il-ħjut huma użati ta 'spiss u f'numri kbar, il-pool jespandi biex jissodisfa l-bżonnijiet ta' min iċempel. Meta ma jkunx hemm ħjut ħielsa fil-ġabra fil-ħin it-tajjeb, jew tistenna li wieħed mill-ħjut jerġa 'lura, jew joħloq wieħed ġdid. Minn dan isegwi li l-grupp tal-ħajt huwa kbir għal xi azzjonijiet għal żmien qasir u mhux adattat għal operazzjonijiet li jaħdmu bħala servizzi matul l-operat kollu tal-applikazzjoni.

Biex tuża thread mill-pool, hemm metodu QueueUserWorkItem li jaċċetta delegat tat-tip WaitCallback, li għandu l-istess firma bħal ParametrizedThreadStart, u l-parametru mgħoddi lilu jwettaq l-istess funzjoni.

ThreadPool.QueueUserWorkItem(...);

Il-metodu ta' grupp ta' threads inqas magħruf RegisterWaitForSingleObject jintuża biex jorganizza operazzjonijiet IO li ma jimblukkawx. Id-delegat mgħoddi għal dan il-metodu se jissejjaħ meta l-WaitHandle mgħoddi lill-metodu huwa "Released".

ThreadPool.RegisterWaitForSingleObject(...)

.NET għandu tajmer tal-ħajt u huwa differenti minn tajmers WinForms/WPF peress li l-handler tiegħu se jissejjaħ fuq ħajt meħud mill-pool.

System.Threading.Timer

Hemm ukoll mod pjuttost eżotiku biex tibgħat delegat għall-eżekuzzjoni għal ħajta mill-pool - il-metodu BeginInvoke.

DelegateInstance.BeginInvoke

Nixtieq nitkellem fil-qosor fuq il-funzjoni li għaliha ħafna mill-metodi ta 'hawn fuq jistgħu jissejħu - CreateThread minn Kernel32.dll Win32 API. Hemm mod, grazzi għall-mekkaniżmu ta 'metodi esterni, biex tissejjaħ din il-funzjoni. Rajt sejħa bħal din darba biss f'eżempju terribbli ta 'kodiċi wirt, u l-motivazzjoni tal-awtur li għamel eżattament dan għadha misteru għalija.

Kernel32.dll CreateThread

Wiri u Debugging Threads

Threads maħluqa minnek, il-komponenti kollha ta 'partijiet terzi, u l-grupp .NET jistgħu jarawha fit-tieqa Threads ta' Visual Studio. Din it-tieqa se turi biss informazzjoni tal-ħajt meta l-applikazzjoni tkun taħt debug u fil-mod Break. Hawnhekk tista 'tara b'mod konvenjenti l-ismijiet tal-munzell u l-prijoritajiet ta' kull ħajta, u taqleb id-debugging għal ħajt speċifiku. Bl-użu tal-proprjetà Prijorità tal-klassi Thread, tista 'tissettja l-prijorità ta' thread, li l-OC u CLR se jipperċepixxu bħala rakkomandazzjoni meta jaqsmu l-ħin tal-proċessur bejn il-ħjut.

.NET: Għodda biex taħdem b'multithreading u asinkronija. Parti 1

Librerija Parallel tal-Kompitu

Task Parallel Library (TPL) ġiet introdotta f'.NET 4.0. Issa huwa l-istandard u l-għodda ewlenija biex taħdem bl-asinkronija. Kwalunkwe kodiċi li juża approċċ antik huwa meqjus bħala wirt. L-unità bażika ta 'TPL hija l-klassi Task mill-ispazju tal-isem System.Threading.Tasks. Kompitu huwa astrazzjoni fuq ħajta. Bil-verżjoni l-ġdida tal-lingwa C#, sirna mod eleganti biex naħdmu ma 'Tasks - async/wait operators. Dawn il-kunċetti għamluha possibbli li tikteb kodiċi mhux sinkroniku bħallikieku kien sempliċi u sinkroniku, dan għamilha possibbli anke għal nies bi ftit fehim tal-ħidma interna tal-ħjut biex jiktbu applikazzjonijiet li jużawhom, applikazzjonijiet li ma jiffriżawx meta jwettqu operazzjonijiet twal. L-użu ta’ async/wait huwa suġġett għal artiklu wieħed jew saħansitra diversi, iżda ser nipprova nikseb il-qofol tiegħu fi ftit sentenzi:

  • async huwa modifikatur ta' metodu li jirritorna Task jew null
  • u tistenna huwa operatur ta 'stennija Task li ma jimblokkax.

Għal darb'oħra: l-operatur await, fil-każ ġenerali (hemm eċċezzjonijiet), se jirrilaxxa l-ħajt kurrenti ta 'eżekuzzjoni aktar, u meta l-Kompitu jispiċċa l-eżekuzzjoni tiegħu, u l-ħajt (fil-fatt, ikun aktar korrett li jingħad il-kuntest , iżda aktar dwar dan aktar tard) se tkompli tesegwixxi l-metodu aktar. Ġewwa .NET, dan il-mekkaniżmu huwa implimentat bl-istess mod bħar-ritorn tar-rendiment, meta l-metodu bil-miktub jinbidel fi klassi sħiħa, li hija magna tal-istat u tista 'tiġi eżegwita f'biċċiet separati skont dawn l-istati. Kull min hu interessat jista’ jikteb kwalunkwe kodiċi sempliċi billi juża asynс/wait, jikkompila u jara l-assemblaġġ billi juża JetBrains dotPeek bil-Kodiċi Ġenerat tal-Kompilatur attivat.

Ejja nħarsu lejn l-għażliet għat-tnedija u l-użu ta 'Kompitu. Fl-eżempju tal-kodiċi hawn taħt, noħolqu kompitu ġdid li ma jagħmel xejn utli (Thread.Sleep (10000)), iżda fil-ħajja reali dan għandu jkun xi xogħol kumpless ta' CPU intensiv.

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
}

Jinħoloq Kompitu b'numru ta' għażliet:

  • LongRunning huwa ħjiel li l-kompitu mhux se jitlesta malajr, li jfisser li jista 'jkun tajjeb li tikkunsidra li ma tieħux ħajta mill-pool, iżda toħloq waħda separata għal dan il-Kompitu sabiex ma ssirx ħsara lill-oħrajn.
  • AttachedToParent - Il-kompiti jistgħu jiġu rranġati f'ġerarkija. Jekk intużat din l-għażla, allura l-Kompitu jista 'jkun fi stat fejn huwa stess ikun lest u qed jistenna l-eżekuzzjoni tat-tfal tiegħu.
  • PreferFairness - ifisser li jkun aħjar li tesegwixxi Kompiti mibgħuta għall-eżekuzzjoni qabel dawk mibgħuta aktar tard. Iżda din hija biss rakkomandazzjoni u r-riżultati mhumiex garantiti.

It-tieni parametru mgħoddi lill-metodu huwa CancellationToken. Biex tittratta b'mod korrett il-kanċellazzjoni ta 'operazzjoni wara li tkun bdiet, il-kodiċi li qed jiġi eżegwit għandu jimtela b'kontrolli għall-istat CancellationToken. Jekk ma jkunx hemm kontrolli, allura l-metodu Ikkanċella msejjaħ fuq l-oġġett CancellationTokenSource se jkun jista 'jwaqqaf l-eżekuzzjoni tat-Task biss qabel ma jibda.

L-aħħar parametru huwa oġġett Scheduler tat-tip TaskScheduler. Din il-klassi u d-dixxendenti tagħha huma ddisinjati biex jimmaniġġjaw strateġiji għad-distribuzzjoni tal-Kompiti fuq il-ħjut; b'mod awtomatiku, il-Kompitu se jiġi esegwit fuq ħajt każwali mill-pool.

L-operatur await huwa applikat għat-Task maħluqa, li jfisser li l-kodiċi miktub wara, jekk ikun hemm wieħed, se jiġi eżegwit fl-istess kuntest (ħafna drabi dan ifisser fuq l-istess ħajta) bħall-kodiċi qabel tistenna.

Il-metodu huwa mmarkat bħala async void, li jfisser li jista 'juża l-operatur await, iżda l-kodiċi tas-sejħa mhux se jkun jista' jistenna għall-eżekuzzjoni. Jekk tali karatteristika hija meħtieġa, allura l-metodu għandu jirritorna Task. Metodi mmarkati async void huma pjuttost komuni: bħala regola, dawn huma handlers ta 'avvenimenti jew metodi oħra li jaħdmu fuq il-prinċipju tan-nar u tinsa. Jekk għandek bżonn mhux biss tagħti l-opportunità li tistenna sat-tmiem tal-eżekuzzjoni, iżda wkoll tirritorna r-riżultat, allura trid tuża Task.

Fuq il-Kompitu li l-metodu StartNew irritorna, kif ukoll fuq kwalunkwe ieħor, tista 'ċċempel il-metodu ConfigureAwait bil-parametru falz, allura l-eżekuzzjoni wara tistenna se tkompli mhux fuq il-kuntest maqbud, iżda fuq wieħed arbitrarju. Dan għandu dejjem isir meta l-kuntest ta 'eżekuzzjoni ma jkunx importanti għall-kodiċi wara tistenna. Din hija wkoll rakkomandazzjoni mill-SM meta tikteb kodiċi li se jitwassal ippakkjat f'librerija.

Ejja nitkellmu ftit aktar dwar kif tista 'tistenna għat-tlestija ta' Kompitu. Hawn taħt hawn eżempju ta 'kodiċi, b'kummenti dwar meta l-aspettattiva ssir kondizzjonalment tajjeb u meta ssir kondizzjonalment ħażin.

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
}

Fl-ewwel eżempju, nistennew li l-Kompitu jitlesta mingħajr ma nibblokka l-ħajt tas-sejħa; se nerġgħu nipproċessaw ir-riżultat biss meta jkun diġà hemm; sa dak iż-żmien, il-ħajt tas-sejħa jitħalla f'idejh.

Fit-tieni għażla, aħna nibblukkaw il-ħajt tas-sejħa sakemm jiġi kkalkulat ir-riżultat tal-metodu. Dan huwa ħażin mhux biss għax okkupajna ħajta, riżorsa tant siewja tal-programm, b'idleness sempliċi, iżda wkoll għaliex jekk il-kodiċi tal-metodu li nsejħu fih tistenna, u l-kuntest ta 'sinkronizzazzjoni jeħtieġ li terġa' lura għall-ħajta li ssejjaħ wara stenna, allura se jkollna staġnar : Il-ħajta li ssejjaħ jistenna waqt li r-riżultat tal-metodu asinkroniku jiġi kkalkulat, il-metodu asinkronu jipprova għalxejn ikompli l-eżekuzzjoni tiegħu fil-ħajta li ssejjaħ.

Żvantaġġ ieħor ta 'dan l-approċċ huwa l-immaniġġjar tal-iżbalji kkumplikat. Il-fatt hu li l-iżbalji fil-kodiċi mhux sinkroniku meta tuża async/wait huma faċli ħafna biex jiġu mmaniġġjati - iġibu ruħhom l-istess bħallikieku l-kodiċi kien sinkroniku. Filwaqt li jekk napplikaw eżorċiżmu ta’ stennija sinkroniku għal Task, l-eċċezzjoni oriġinali tinbidel f’AggregateException, i.e. Biex timmaniġġja l-eċċezzjoni, ser ikollok teżamina t-tip InnerException u tikteb katina if lilek innifsek ġewwa blokka waħda ta 'qabda jew tuża l-qabda meta tibni, minflok il-katina ta' blokki ta 'qabda li hija aktar familjari fid-dinja C#.

It-tielet u l-aħħar eżempji huma wkoll immarkati bħala ħżiena għall-istess raġuni u fihom l-istess problemi kollha.

Il-metodi WhenAny u WhenAll huma estremament konvenjenti biex wieħed jistenna grupp ta 'Kompiti; ikebbeb grupp ta' Kompiti f'wieħed, li jispara jew meta Task mill-grupp jiġi attivat għall-ewwel darba, jew meta kollha jkunu temmew l-eżekuzzjoni tagħhom.

Waqfien tal-ħjut

Għal diversi raġunijiet, jista 'jkun meħtieġ li twaqqaf il-fluss wara li jkun beda. Hemm għadd ta' modi kif tagħmel dan. Il-klassi Thread għandha żewġ metodi msemmijin b'mod xieraq: Aborta и Tinterrompi. L-ewwel wieħed mhux rakkomandat ħafna għall-użu, minħabba wara li ssejħilha fi kwalunkwe mument każwali, waqt l-ipproċessar ta 'kwalunkwe istruzzjoni, tintefa' eċċezzjoni ThreadAbortedException. Ma tistennax li eċċezzjoni bħal din tintefa' meta żżid xi varjabbli numru sħiħ, hux? U meta tuża dan il-metodu, din hija sitwazzjoni reali ħafna. Jekk għandek bżonn tipprevjeni lis-CLR milli jiġġenera eċċezzjoni bħal din f'ċerta sezzjoni tal-kodiċi, tista 'tkebbebha f'sejħiet Thread.BeginCriticalRegion, Thread.EndCriticalRegion. Kwalunkwe kodiċi miktub fi blokk finally huwa mgeżwer f'sejħiet bħal dawn. Għal din ir-raġuni, fil-fond tal-kodiċi qafas tista 'ssib blokki bi prova vojta, iżda mhux vojta finalment. Microsoft tiskoraġġixxi dan il-metodu tant li ma inkludewx fil-qalba .net.

Il-metodu Interrupt jaħdem b'mod aktar prevedibbli. Jista 'jinterrompi l-ħajta b'eċċezzjoni ThreadInterruptedException biss matul dawk il-mumenti meta l-ħajta tkun fi stat ta’ stennija. Jidħol f'dan l-istat waqt li jiddendel waqt li jistenna WaitHandle, lock, jew wara li jsejjaħ Thread.Sleep.

Iż-żewġ għażliet deskritti hawn fuq huma ħżiena minħabba l-imprevedibbiltà tagħhom. Is-soluzzjoni hija li tuża struttura CancellationToken u l-klassi CancellationTokenSource. Il-punt huwa dan: tinħoloq każ tal-klassi CancellationTokenSource u dak li għandu biss jista’ jwaqqaf l-operazzjoni billi jsejjaħ il-metodu Ikkanċella. Il-CancellationToken biss jiġi mgħoddi lill-operazzjoni nnifisha. Is-sidien ta' CancellationToken ma jistgħux jikkanċellaw l-operazzjoni huma stess, iżda jistgħu jivverifikaw biss jekk l-operazzjoni ġietx ikkanċellata. Hemm proprjetà Boolean għal dan IsCancellationRequested u l-metodu ThrowIfCancelRequested. Dan tal-aħħar se tarmi eċċezzjoni TaskCancelledException jekk il-metodu Ikkanċella kien imsejjaħ fuq l-istanza CancellationToken li qed tiġi parroted. U dan huwa l-metodu li nirrakkomanda li nuża. Dan huwa titjib fuq l-għażliet preċedenti billi jinkiseb kontroll sħiħ fuq f'liema punt operazzjoni ta 'eċċezzjoni tista' tiġi abortita.

L-iktar għażla brutali biex titwaqqaf ħajta hija li ssejjaħ il-funzjoni Win32 API TerminateThread. L-imġieba tas-CLR wara li ssejjaħ din il-funzjoni tista' tkun imprevedibbli. Fuq MSDN hemm miktub dan li ġej dwar din il-funzjoni: “TerminateThread hija funzjoni perikoluża li għandha tintuża biss fl-aktar każijiet estremi. “

Tikkonverti API legacy għal Task Based bl-użu tal-metodu FromAsync

Jekk kont xortik tajba biżżejjed li taħdem fuq proġett li nbeda wara li l-Kompiti ġew introdotti u ma baqgħux jikkawżaw orrur kwiet għall-biċċa l-kbira tal-iżviluppaturi, allura ma jkollokx għalfejn tittratta ma 'ħafna APIs qodma, kemm dawk ta' partijiet terzi kif ukoll dawk tat-tim tiegħek. tkun ittorturata fil-passat. Fortunatament, it-tim tal-.NET Framework ħa ħsiebna, għalkemm forsi l-għan kien li nieħdu ħsiebna nfusna. Ikun xi jkun, .NET għandu numru ta 'għodod għall-konverżjoni bla tbatija kodiċi miktuba fl-approċċi qodma ta' programmazzjoni mhux sinkroniku għal dak il-ġdid. Wieħed minnhom huwa l-metodu FromAsync ta 'TaskFactory. Fl-eżempju tal-kodiċi hawn taħt, nagħlaq il-metodi async qodma tal-klassi WebRequest f'Kompitu billi tuża dan il-metodu.

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

Dan huwa biss eżempju u mhux probabbli li jkollok tagħmel dan b'tipi integrati, iżda kwalunkwe proġett antik huwa sempliċiment mimli b'metodi BeginDoSomething li jirritornaw metodi IAsyncResult u EndDoSomething li jirċevuh.

Ikkonverti API legacy għal Task Based billi tuża l-klassi TaskCompletionSource

Għodda oħra importanti li għandek tikkonsidra hija l-klassi TaskCompletionSource. F'termini ta 'funzjonijiet, għan u prinċipju ta' tħaddim, jista 'jkun kemmxejn reminixxenti tal-metodu RegisterWaitForSingleObject tal-klassi ThreadPool, li ktibt dwaru hawn fuq. Billi tuża din il-klassi, tista 'faċilment u konvenjenti wrap APIs asinkroniċi qodma f'Tasks.

Tgħid li diġà tkellimt dwar il-metodu FromAsync tal-klassi TaskFactory maħsuba għal dawn l-għanijiet. Hawnhekk ser ikollna niftakru l-istorja kollha tal-iżvilupp ta 'mudelli asinkroniċi f'.net li Microsoft offriet matul l-aħħar 15-il sena: qabel it-Task-Based Asynchronous Pattern (TAP), kien hemm il-Disinn ta' Programmazzjoni Asynchronous (APP), li kien dwar metodi TibdaJagħmel xi ħaġa lura IAsyncResult u metodi tmiemAgħmel xi ħaġa li taċċettaha u għall-wirt ta 'dawn is-snin il-metodu FromAsync huwa biss perfett, iżda maż-żmien, ġie sostitwit mill-Mudell Asinkroniku Ibbażat fuq Avvenimenti (U AP), li assumiet li avveniment se jitqajjem meta titlesta l-operazzjoni asinkronika.

TaskCompletionSource hija perfetta għat-tgeżwir ta' Tasks u APIs legacy mibnija madwar il-mudell tal-avveniment. L-essenza tax-xogħol tagħha hija kif ġej: oġġett ta 'din il-klassi għandu proprjetà pubblika tat-tip Task, li l-istat tagħha jista' jiġi kkontrollat ​​permezz tal-metodi SetResult, SetException, eċċ tal-klassi TaskCompletionSource. F'postijiet fejn l-operatur await ġie applikat għal dan il-Kompitu, se jiġi eżegwit jew ifalli b'eċċezzjoni skont il-metodu applikat għat-TaskCompletionSource. Jekk għadu mhux ċar, ejja nħarsu lejn dan l-eżempju tal-kodiċi, fejn xi API EAP antik hija mgeżwra f'Kompitu bl-użu ta 'TaskCompletionSource: meta l-avveniment jispara, il-Kompitu jiġi trasferit għall-istat Completed, u l-metodu li applika l-operatur await. għal dan il-Kompitu se jerġa' jibda l-eżekuzzjoni tiegħu wara li jkun irċieva l-oġġett tirriżulta.

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

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

    result completionSource.Task;
}

TaskCompletionSource Tips & Tricks

It-tgeżwir ta 'APIs qodma mhuwiex dak kollu li jista' jsir billi tuża TaskCompletionSource. L-użu ta 'din il-klassi jiftaħ possibbiltà interessanti li jiġu ddisinjati diversi APIs fuq Kompiti li mhumiex okkupati minn ħjut. U n-nixxiegħa, kif niftakru, hija riżorsa għalja u n-numru tagħhom huwa limitat (prinċipalment bl-ammont ta 'RAM). Din il-limitazzjoni tista' tinkiseb faċilment billi tiġi żviluppata, pereżempju, applikazzjoni tal-web mgħobbija b'loġika kummerċjali kumplessa. Ejja nikkunsidraw il-possibbiltajiet li qed nitkellem dwarhom meta nimplimenta trick bħal Long-Polling.

Fil-qosor, l-essenza tal-trick hija din: għandek bżonn tirċievi informazzjoni mill-API dwar xi avvenimenti li jseħħu fuq in-naħa tagħha, filwaqt li l-API, għal xi raġuni, ma tistax tirrapporta l-avveniment, iżda tista 'biss tirritorna l-istat. Eżempju ta 'dawn huma l-APIs kollha mibnija fuq HTTP qabel iż-żminijiet ta' WebSocket jew meta kien impossibbli għal xi raġuni li tuża din it-teknoloġija. Il-klijent jista' jistaqsi lis-server HTTP. Is-server HTTP ma jistax hu stess jibda komunikazzjoni mal-klijent. Soluzzjoni sempliċi hija li tivverifika s-server bl-użu ta’ tajmer, iżda dan joħloq tagħbija addizzjonali fuq is-server u dewmien addizzjonali bħala medja TimerInterval / 2. Biex taqleb dan, ġie ivvintat trick imsejjaħ Long Polling, li jinvolvi dewmien tar-rispons minn is-server sakemm jiskadi l-Timeout jew se jseħħ avveniment. Jekk l-avveniment ikun seħħ, allura jiġi pproċessat, jekk le, allura t-talba terġa' tintbagħat.

while(!eventOccures && !timeoutExceeded)  {

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

Iżda soluzzjoni bħal din se tkun terribbli hekk kif in-numru ta 'klijenti li qed jistennew l-avveniment jiżdied, għaliex... Kull klijent bħal dan jokkupa ħajt kollu jistenna għal avveniment. Iva, u nġibu dewmien addizzjonali ta '1ms meta l-avveniment jiġi attivat, ħafna drabi dan mhux sinifikanti, imma għaliex is-software isir agħar milli jista' jkun? Jekk inneħħu Thread.Sleep(1), allura għalxejn se tagħbija qalba waħda tal-proċessur 100% idle, li ddur f'ċiklu inutli. Billi tuża TaskCompletionSource tista' faċilment terġa' tagħmel dan il-kodiċi u ssolvi l-problemi kollha identifikati hawn fuq:

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

Dan il-kodiċi mhuwiex lest għall-produzzjoni, iżda biss demo. Biex tużah f'każijiet reali, għandek bżonn ukoll, mill-inqas, li timmaniġġja s-sitwazzjoni meta jasal messaġġ fi żmien meta ħadd ma jkun qed jistennieh: f'dan il-każ, il-metodu AsseptMessageAsync għandu jirritorna Task diġà komplut. Jekk dan huwa l-aktar każ komuni, allura tista 'taħseb dwar l-użu ta' ValueTask.

Meta nirċievu talba għal messaġġ, noħolqu u npoġġu TaskCompletionSource fid-dizzjunarju, u mbagħad nistennew x'jiġri l-ewwel: l-intervall ta 'żmien speċifikat jiskadi jew jiġi riċevut messaġġ.

ValueTask: għaliex u kif

L-operaturi async/wait, bħall-operatur tar-ritorn tar-rendiment, jiġġeneraw magna tal-istat mill-metodu, u dan huwa l-ħolqien ta 'oġġett ġdid, li kważi dejjem mhuwiex importanti, iżda f'każijiet rari jista' joħloq problema. Dan il-każ jista 'jkun metodu li jissejjaħ verament spiss, qed nitkellmu dwar għexieren u mijiet ta' eluf ta 'sejħiet kull sekonda. Jekk metodu bħal dan jinkiteb b'tali mod li fil-biċċa l-kbira tal-każijiet jirritorna riżultat li jinjora l-metodi kollha ta 'wait, allura .NET jipprovdi għodda biex tottimizza dan - l-istruttura ValueTask. Biex tagħmilha ċara, ejja nħarsu lejn eżempju tal-użu tiegħu: hemm cache li mmorru fiha ħafna drabi. Hemm xi valuri fiha u mbagħad sempliċement nirritornawhom; jekk le, allura mmorru f'xi IO bil-mod biex niksbuhom. Irrid nagħmel dan tal-aħħar b'mod asinkroniku, li jfisser li l-metodu kollu jirriżulta li jkun asinkroniku. Għalhekk, il-mod ovvju biex tikteb il-metodu huwa kif ġej:

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

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

Minħabba x-xewqa li tottimizza ftit, u biża' żgħira ta 'dak li se tiġġenera Roslyn meta tikkumpila dan il-kodiċi, tista' tikteb mill-ġdid dan l-eżempju kif ġej:

public Task<string> GetById(int id) {

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

Tabilħaqq, l-aħjar soluzzjoni f'dan il-każ tkun li jiġi ottimizzat il-hot-path, jiġifieri, il-kisba ta 'valur mid-dizzjunarju mingħajr ebda allokazzjoni bla bżonn u tagħbija fuq il-GC, filwaqt li f'dawk il-każijiet rari meta għad għandna bżonn immorru għal IO għad-data , kollox se jibqa 'plus /minus il-mod antik:

public ValueTask<string> GetById(int id) {

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

Ejja nagħtu ħarsa aktar mill-qrib lejn din il-biċċa ta 'kodiċi: jekk hemm valur fil-cache, noħolqu struttura, inkella l-kompitu reali jkun imgeżwer f'waħda sinifikanti. Il-kodiċi tas-sejħa ma jimpurtax f'liema triq ġie esegwit dan il-kodiċi: ValueTask, mil-lat tas-sintassi C#, se jġib ruħu l-istess bħal Task regolari f'dan il-każ.

TaskSchedulers: ġestjoni ta' strateġiji ta' tnedija tal-kompitu

L-API li jmiss li nixtieq nikkunsidra hija l-klassi TaskScheduler u d-derivattivi tagħha. Diġà semmejt hawn fuq li TPL għandu l-abbiltà li jimmaniġġja strateġiji għad-distribuzzjoni tal-Kompiti fuq il-ħjut. Tali strateġiji huma definiti fid-dixxendenti tal-klassi TaskScheduler. Kważi kull strateġija li jista 'jkollok bżonn tista' tinstab fil-librerija. ParallelExtensionsExtras, żviluppat minn Microsoft, iżda mhux parti minn .NET, iżda fornut bħala pakkett Nuget. Ejja nħarsu fil-qosor lejn xi wħud minnhom:

  • CurrentThreadTaskScheduler — tesegwixxi Ħidmiet fuq il-ħajt kurrenti
  • LimitedConcurrencyLevelTaskScheduler — tillimita n-numru ta’ Ħidmiet imwettqa simultanjament mill-parametru N, li huwa aċċettat fil-kostruttur
  • OrdnatTaskScheduler — hija definita bħala LimitedConcurrencyLevelTaskScheduler(1), għalhekk il-kompiti se jiġu esegwiti b'mod sekwenzjali.
  • WorkStealingTaskScheduler - timplimenti serq tax-xogħol approċċ għad-distribuzzjoni tal-kompiti. Essenzjalment huwa ThreadPool separat. Issolvi l-problema li f'.NET ThreadPool hija klassi statika, waħda għall-applikazzjonijiet kollha, li jfisser li t-tagħbija żejda tagħha jew l-użu ħażin f'parti waħda tal-programm jistgħu jwasslu għal effetti sekondarji f'oħra. Barra minn hekk, huwa estremament diffiċli li wieħed jifhem il-kawża ta 'difetti bħal dawn. Dik. Jista' jkun hemm bżonn li jintużaw WorkStealingTaskSchedulers separati f'partijiet tal-programm fejn l-użu ta' ThreadPool jista' jkun aggressiv u imprevedibbli.
  • QueuedTaskScheduler — jippermettilek twettaq il-kompiti skont ir-regoli tal-kju ta’ prijorità
  • ThreadPerTaskScheduler — joħloq ħajt separat għal kull Task li jiġi esegwit fuqu. Jista 'jkun utli għal kompiti li jieħdu żmien twil b'mod imprevedibbli biex jitlestew.

Hemm dettaljat tajjeb artikolu dwar TaskSchedulers fuq il-blog tal-Microsoft.

Għal debugging konvenjenti ta 'dak kollu relatat mal-Ħidmiet, Visual Studio għandu tieqa tal-Kompiti. F'din it-tieqa tista 'tara l-istat attwali tal-kompitu u taqbeż għal-linja tal-kodiċi li qed tesegwixxi bħalissa.

.NET: Għodda biex taħdem b'multithreading u asinkronija. Parti 1

PLinq u l-klassi Parallel

Minbarra l-Kompiti u dak kollu li ntqal dwarhom, hemm żewġ għodod oħra interessanti f’.NET: PLinq (Linq2Parallel) u l-klassi Parallel. L-ewwel iwiegħed eżekuzzjoni parallela tal-operazzjonijiet kollha Linq fuq ħjut multipli. In-numru ta 'ħjut jista' jiġi kkonfigurat bl-użu tal-metodu ta 'estensjoni WithDegreeOfParallelism. Sfortunatament, ħafna drabi PLinq fil-modalità default tiegħu ma jkollux biżżejjed informazzjoni dwar l-intern tas-sors tad-dejta tiegħek biex jipprovdi qligħ sinifikanti fil-veloċità, min-naħa l-oħra, l-ispiża biex tipprova hija baxxa ħafna: għandek bżonn biss li ċċempel il-metodu AsParallel qabel il-katina tal-metodi Linq u tmexxi testijiet tal-prestazzjoni. Barra minn hekk, huwa possibbli li tgħaddi informazzjoni addizzjonali lil PLinq dwar in-natura tas-sors tad-dejta tiegħek billi tuża l-mekkaniżmu tal-Ħitan. Tista' taqra aktar hawn и hawn.

Il-klassi statika Parallel tipprovdi metodi għall-iterazzjoni permezz ta 'ġbir Foreach b'mod parallel, tesegwixxi For loop, u teżegwixxi delegati multipli b'mod parallel Invoke. L-eżekuzzjoni tal-ħajt kurrenti se titwaqqaf sakemm jitlestew il-kalkoli. In-numru ta 'ħjut jista' jiġi kkonfigurat billi tgħaddi ParallelOptions bħala l-aħħar argument. Tista 'wkoll tispeċifika TaskScheduler u CancellationToken billi tuża għażliet.

Sejbiet

Meta bdejt nikteb dan l-artiklu bbażat fuq il-materjali tar-rapport tiegħi u l-informazzjoni li ġbart waqt ix-xogħol tiegħi wara, ma stennejtx li jkun hemm daqshekk minnu. Issa, meta l-editur tat-test li fih qed nittajpja dan l-artiklu bi tpattija jgħidli li l-paġna 15 tkun marret, ser niġbor fil-qosor ir-riżultati interim. Tricks oħra, APIs, għodod viżwali u nases se jkunu koperti fl-artiklu li jmiss.

Konklużjonijiet:

  • Ikollok bżonn tkun taf l-għodod biex taħdem ma 'ħjut, asinkronija u paralleliżmu sabiex tuża r-riżorsi ta' PCs moderni.
  • .NET għandu ħafna għodod differenti għal dawn l-għanijiet
  • Mhux kollha dehru f'daqqa, għalhekk ħafna drabi tista 'ssib dawk tal-wirt, madankollu, hemm modi kif tikkonverti APIs qodma mingħajr ħafna sforz.
  • Il-ħidma bil-ħjut f'.NET hija rappreżentata mill-klassijiet Thread u ThreadPool
  • Il-metodi Thread.Abort, Thread.Interrupt, u ​​Win32 API TerminateThread huma perikolużi u mhumiex rakkomandati għall-użu. Minflok, huwa aħjar li tuża l-mekkaniżmu CancellationToken
  • Il-fluss huwa riżors siewi u l-provvista tiegħu hija limitata. Sitwazzjonijiet fejn il-ħjut huma okkupati jistennew avvenimenti għandhom jiġu evitati. Għal dan huwa konvenjenti li tuża l-klassi TaskCompletionSource
  • L-għodda .NET l-aktar qawwija u avvanzata biex taħdem b'paralleliżmu u asinkronija huma Tasks.
  • L-operaturi c# async/wait jimplimentaw il-kunċett ta’ stennija li ma timblokkax
  • Tista 'tikkontrolla d-distribuzzjoni tal-Kompiti fuq il-ħjut billi tuża klassijiet derivati ​​minn TaskScheduler
  • L-istruttura ValueTask tista' tkun utli fl-ottimizzazzjoni tal-hot-paths u t-traffiku tal-memorja
  • It-twieqi tal-Ħidmiet u l-Ħjut ta' Visual Studio jipprovdu ħafna informazzjoni utli għad-debugging ta' kodiċi multi-threaded jew mhux sinkroniku
  • PLinq hija għodda friska, iżda jista 'ma jkollux biżżejjed informazzjoni dwar is-sors tad-dejta tiegħek, iżda dan jista' jiġi ffissat bl-użu tal-mekkaniżmu ta 'qsim
  • Biex titkompla ...

Sors: www.habr.com

Żid kumment