.NET: Amûrên ji bo xebitandina bi multithreading û asynchrony. Beş 1

Ez gotara orîjînal li ser Habrê diweşînim, ku wergera wê di nav pargîdaniyê de hatiye weşandin posta blogê.

Pêwîstiya kirina tiştekî asynkron, bêyî ku li vir û niha li benda encamê bimîne, an jî dabeşkirina karên mezin di nav çend yekîneyên ku wê dikin de, berî hatina komputeran hebû. Bi hatina wan re, ev hewcedarî gelekî berçav bû. Naha, di sala 2019-an de, ez vê gotarê li ser laptopek bi pêvajoyek 8-core Intel Core dinivîsim, ku li ser wê zêdetirî sed pêvajo bi hev re dimeşin, û hêj bêtir mijar. Nêzîkî, têlefonek piçek şil heye, ku çend sal berê kirî, pêvajoyek wê ya 8-core li ser heye. Çavkaniyên tematîk bi gotar û vîdyoyan tije ne ku nivîskarên wan heyranê têlefonên ala yên îsal ên ku pêvajoyên 16-core vedigirin. MS Azure makîneyek virtual bi pêvajoyek bingehîn a 20 û 128 TB RAM bi kêmtirî 2 $/saetê peyda dike. Mixabin, ne mimkun e ku meriv herî zêde derxe û vê hêzê bi kar bîne bêyî ku meriv pêwendiya têlan birêve bibe.

Terminolojiyê

Doz - Tişta OS, cîhê navnîşana veqetandî, têlan vedihewîne.
Dezî - Tiştek OS-ê, yekîneya herî piçûk a darvekirinê, parçeyek pêvajoyek, têl di nav pêvajoyek de bîranîn û çavkaniyên din di nav xwe de parve dikin.
Multitasking - Taybetmendiya OS, şiyana meşandina çend pêvajoyan bi hevdemî
Pir-core - Taybetmendiyek pêvajoyê, şiyana ku ji bo hilberandina daneyê gelek core bikar bîne
Multiprocessing - Taybetmendiyek komputerê, şiyana ku bi hevdemî bi çend pêvajoyan re bi fîzîkî re bixebite
Multithreading - Taybetmendiyek pêvajoyek, şiyana belavkirina danûstendina daneyê di nav çend mijaran de.
Paralelîzm - di yekîneya demê de bi hevdemî çend çalakiyan bi fizîkî pêk tîne
Asynkronî - pêkanîna operasyonek bêyî ku li benda qedandina vê pêvajoyê bimîne; encama darvekirinê dikare paşê were pêvajo kirin.

Metafor

Hemî pênase ne baş in û hin hewcedarî ravekirina zêde ne, ji ber vê yekê ez ê metaforek di derbarê xwarina taştê de li termînolojiya ku bi fermî hatî destnîşan kirin zêde bikim. Di vê metaforê de çêkirina taştê pêvajoyek e.

Dema ez serê sibê taştê amade dikim (CPU) Ez têm metbexê (Computer). 2 destên min hene (cores). Di metbexê de gelek amûr hene (IO): firn, tenûr, toster, sarinc. Ez gazê vedikim, tava firingiyê datînim ser û rûn dirijînim bêyî ku li bendê bim ku germ bibe (asynchronously, Non-Blocking-IO-Bisekine), ez hêkan ji sarincokê derdixim û dipelixînim, paşê bi destekî lêdixim (Mijara #1), û duyemîn (Mijara #2) hilgirtina plakaya (Çavkaniya Hevbeş). Naha ez dixwazim çaydankê vekim, lê têra destên min nînin (Mijara Birçîbûnê) Di vê demê de, tava firingî germ dibe (Pêvajoya encamê) ya ku min lêxistiye dirijînim. Ez xwe digihînim çaydankê û wê vedikim û bi ehmeqî li ava ku tê de keliye temaşe dikim (Astengkirin-IO-Li bendê bin), her çend di vê demê de wî dikaribû plakaya ku li qamçiyê omlet lê dixist bişo.

Min omletek bi tenê bi 2 destan pijand, lê zêde jî tune, lê di heman demê de, di dema qamçkirina omletê de, 3 operasyon bi yekcarî pêk hatin: qamçkirina omletê, girtina tabloyê, germkirina tavê. CPU beşa herî bilez a komputerê ye, IO ew e ku pir caran her tişt hêdî dibe, ji ber vê yekê pir caran çareseriyek bi bandor ew e ku dema ku daneya ji IO distîne CPU bi tiştek re dagîr bike.

Berdewamiya metaforê:

  • Ger di pêvajoya amadekirina omletê de, ez ê jî hewl bidim ku cil biguherim, ev ê bibe mînakek pirzimanî. Nîşanek girîng: komputer di vê yekê de ji mirovan pir çêtir in.
  • Metbexek bi çend aşpêjvan re, mînakî li xwaringehekê - komputerek pir-core.
  • Gelek xwaringeh li dadgehek xwarinê li navendek danûstendinê - navenda daneyê

Amûrên .NET

.NET wek gelek tiştên din di xebata bi têlan de baş e. Bi her guhertoya nû re, ew ji bo xebata bi wan re her ku diçe amûrên nû, qatên nû yên abstraction li ser mijarên OS-ê destnîşan dike. Dema ku bi avakirina abstractionan re dixebitin, pêşdebirên çarçowe nêzîkatiyek bikar tînin ku fersendê dihêle, dema ku abstrakasyonek astek bilind bikar tîne, dakeve yek an çend astek jêrîn. Pirî caran ev ne hewce ye, bi rastî ew derî vedike ku meriv bi tivingê di lingê xwe de biteqîne, lê carinan, di rewşên hindik de, dibe ku ew yekane rê be ji bo çareserkirina pirsgirêkek ku di asta heyî ya abstrakasyonê de nayê çareser kirin. .

Ji amûran re, mebesta min hem navberên bernamesaziya serîlêdanê (API) ku ji hêla çarçove û pakêtên partiya sêyemîn ve têne peyda kirin, hem jî tevahî çareseriyên nermalavê yên ku lêgerîna ji bo pirsgirêkên ku bi koda pir-têkilî ve girêdayî ne hêsan dikin.

Mijarek dest pê dike

Dersa Thread di .NET-ê de ji bo xebata bi têlan re çîna herî bingehîn e. Avaker yek ji du delegeyan qebûl dike:

  • ThreadStart - Parametre tune
  • ParametrizedThreadStart - bi yek parametreyê ji cureyê object.

Delege dê di xêza ku nû hatî afirandin de piştî bangkirina rêbaza Destpêkê were darve kirin. Heke nûnerek ji celebê ParametrizedThreadStart ji çêker re were derbas kirin, wê hingê divê tiştek ji rêbaza Destpêkê re were derbas kirin. Ev mekanîzma ji bo veguheztina agahdariya herêmî li ser çemê pêdivî ye. Hêjayî gotinê ye ku çêkirina têlek operasyonek biha ye, û mijar bixwe tiştek giran e, bi kêmanî ji ber ku ew 1 MB bîranîn li ser stakê veqetîne û pêwendiya bi OS API-yê re hewce dike.

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

Dersa ThreadPool têgeha hewzê temsîl dike. Di .NET-ê de, hewza teşeyê perçeyek endezyariyê ye, û pêşdebirên li Microsoft-ê gelek hewil dane ku pê ewle bibin ku ew di cûrbecûr senaryoyan de bi rengek çêtirîn dixebite.

Têgeha giştî:

Ji gava ku serîlêdan dest pê dike, ew di paşerojê de çend mijarên di rezervan de diafirîne û şiyana wergirtina wan ji bo karanîna peyda dike. Ger têlan bi gelemperî û bi hejmarên mezin têne bikar anîn, hewz berfireh dibe da ku hewcedariyên gazîdar bicîh bîne. Gava ku di wextê rast de di hewzê de mijarên belaş nebin, ew ê yan li benda vegerandina yek ji têlan bimîne, an jî yek nû biafirîne. Wusa dixuye ku hewza Mijarê ji bo hin kiryarên kurt-kurt pir mezin e û ji bo operasyonên ku di tevahiya xebata serîlêdanê de wekî karûbar têne xebitandin nebaş e.

Ji bo ku têlek ji hewzê bikar bînin, rêbazek QueueUserWorkItem heye ku nûnerek celebê WaitCallback qebûl dike, ku heman îmzeya wekî ParametrizedThreadStart heye, û pîvana ku jê re derbas dibe heman fonksiyonê pêk tîne.

ThreadPool.QueueUserWorkItem(...);

Rêbaza hewzê ya kêm-naskirî RegisterWaitForSingleObject ji bo organîzekirina operasyonên IO yên ne-astengkirî tê bikar anîn. Delegeya ku ji vê rêbazê re derbas bûye dê were gazî kirin dema ku WaitHandle ji rêbazê re derbas dibe "Rabe".

ThreadPool.RegisterWaitForSingleObject(...)

.NET xwedan demjimêrek tîrêjê ye û ew ji demjimêrên WinForms/WPF cûda dibe, di wê yekê de ku hilgirê wê dê li ser têlek ku ji hewzê hatî girtin were gazî kirin.

System.Threading.Timer

Di heman demê de rêyek pir biyanî jî heye ku meriv nûnerek ji bo darvekirinê bişîne ser mijarek ji hewzê - rêbaza BeginInvoke.

DelegateInstance.BeginInvoke

Ez dixwazim bi kurtî li ser fonksiyona ku gelek ji rêbazên jorîn jê re têne gotin rawestim - CreateThread ji Kernel32.dll Win32 API. Bi saya mekanîzmaya rêbazên derveyî, rêyek heye ku meriv vê fonksiyonê bang bike. Min bangek wusa tenê carekê di mînakek tirsnak a koda mîras de dît, û motîvasyona nivîskarê ku tam ev kiriye hîn jî ji min re nepenî dimîne.

Kernel32.dll CreateThread

Dîtin û Debugging Mijar

Mijarên ku ji hêla we ve hatine afirandin, hemî pêkhateyên partiya sêyemîn, û hewza .NET dikarin di pencereya Mijaran a Visual Studio de werin dîtin. Dema ku serîlêdan di bin debugkirinê de ye û di moda Veqetandinê de ye, ev pace dê tenê agahdariya mijarê nîşan bide. Li vir hûn dikarin bi rehetî navên stok û pêşîniyên her mijarê bibînin, û debugkirinê veguherînin mijarek taybetî. Bi karanîna taybetmendiya Priority ya çîna Mijara, hûn dikarin pêşîniya mijarekê destnîşan bikin, ku OC û CLR dema ku dema pêvajoyê di navbera mijaran de dabeş dikin dê wekî pêşniyarek fêm bikin.

.NET: Amûrên ji bo xebitandina bi multithreading û asynchrony. Beş 1

Pirtûkxaneya Parallel Task

Pirtûkxaneya Parallel a Peywiran (TPL) di .NET 4.0 de hate destnîşan kirin. Naha ew standard û amûra sereke ye ji bo xebata bi asynkroniyê. Her kodek ku nêzîkatiyek kevntir bikar tîne wekî mîras tê hesibandin. Yekîneya bingehîn a TPL ji qada navên System.Threading.Tasks çîna Task e. Erk li ser têlekê veqetandinek e. Bi guhertoya nû ya zimanê C# re, me rêgezek xweşik a ku bi Tasks re dixebitin - operatorên async/wait stand. Van têgehan îmkana nivîsandina koda asynkron wekî ku ew sade û hevdem be, ev yek hişt ku kesên ku ji xebata hundurîn a têlan kêm têgihiştin jî binivîsin ku serîlêdanên ku wan bikar tînin binivîsin, serîlêdanên ku dema operasyonên dirêj dicemidînin. Bikaranîna async/bendî mijarek ji bo yek an jî çend gotaran e, lê ez ê hewl bidim ku naveroka wê di çend hevokan de bibînim:

  • async guhêrbarek rêbazek e ku Task an betal vedigere
  • û await operatorek li benda Peywirê ne-astengker e.

Carek din: operatorê li bendê, di rewşa gelemperî de (îstîsna hene), dê xêza darvekirinê ya heyî bêtir berde, û gava ku Task cîbicîkirina xwe bi dawî bike, û têxê (bi rastî, dê rasttir be ku meriv li ser çarçovê bêje , lê bêtir li ser wê paşê) dê pêkanîna rêbazê bêtir berdewam bike. Di hundurê .NET-ê de, ev mekanîzma bi heman awayê vegerandina hilberînê tête bicîh kirin, dema ku rêbaza nivîskî vediguhere çînek tevahî, ku makîneyek dewletê ye û li gorî van dewletan dikare di perçeyên cûda de were darve kirin. Her kesê eleqedar dikare bi karanîna asynс/waitê kodek hêsan binivîse, bi karanîna JetBrains dotPeek bi koda Berhevkarê hatî çalak kirin berhev bike û bibîne.

Ka em li vebijarkên ji bo destpêkirin û karanîna Task binêrin. Di mînaka koda jêrîn de, em karekî nû diafirînin ku tiştek bikêr nake (Thread.Sleep(10000)), lê di jiyana rast de divê ev xebatek tevlihev a CPU-dijwar be.

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
}

Peywirek bi çend vebijarkan tê afirandin:

  • LongRunning îşaretek e ku dê peywir zû biqede, ku tê vê wateyê ku dibe ku hêja be ku meriv ji hewzê nekişîne, lê ji bo vê Peywirê yek cihêreng biafirîne da ku zirarê nede yên din.
  • AttachedToParent - Karî dikarin di hiyerarşiyekê de werin rêz kirin. Ger ev vebijark hate bikar anîn, wê hingê dibe ku Task di rewşek de be ku ew bixwe qedandiye û li benda darvekirina zarokên xwe ye.
  • PreferFairness - tê vê wateyê ku dê çêtir be ku Karên ku ji bo darvekirinê berê hatine şandin berî yên ku paşê hatine şandin werin bicîh kirin. Lê ev tenê pêşniyarek e û encam ne garantî ne.

Parametreya duyemîn ku ji rêbazê re derbas dibe CancellationToken e. Ji bo ku betalkirina operasyonek piştî ku dest pê kir bi rêkûpêk bi rê ve bibe, koda ku hatî darve kirin divê bi kontrolên rewşa CancellationToken were dagirtin. Ger kontrol tunebin, wê hingê rêbaza Betalkirinê ya ku li ser objekta CancellationTokenSource tê gazî kirin dê karibe berî ku dest pê bike pêkanîna Karê rawestîne.

Parametreya paşîn objektek plansazker a celebê TaskScheduler e. Ev çîn û dûndana wê ji bo kontrolkirina stratejiyên ji bo belavkirina Peywiran li ser mijaran hatine sêwirandin; Ji hêla xwerû, Task dê li ser mijarek bêserûber ji hewzê were darve kirin.

Operatora bendewariyê li ser Peywira çêkirî tê sepandin, ku tê vê wateyê ku koda ku li dû wê hatî nivîsandin, heke yek hebe, dê di heman çarçoveyê de (pir caran ev tê vê wateyê li ser heman têxê) wekî koda ku li bendê ye were darve kirin.

Rêbaz wekî valahiya async tê nîşankirin, ku tê vê wateyê ku ew dikare operatorê li bendê bikar bîne, lê koda bangê dê nikaribe li benda darvekirinê bimîne. Ger taybetmendiyek wusa hewce ye, wê hingê pêdivî ye ku rêbaz Task vegere. Rêbazên ku valahiya async têne nîşankirin pir gelemperî ne: wekî qaîdeyek, ev rêvekerên bûyerê an rêbazên din in ku li ser agir dixebitin û prensîba ji bîr dikin. Heke hûn hewce ne ku ne tenê fersendê bidin ku heya dawiya darvekirinê bisekinin, lê di heman demê de encamê jî vegerînin, wê hingê hûn hewce ne ku Task bikar bînin.

Li ser Peywira ku rêbaza StartNew vegerandiye, û her weha li ser her yekê din, hûn dikarin rêbaza ConfigureAwait bi pîvana derewîn vebêjin, wê hingê înfaz li dû bendê dê ne li ser çarçoweya hatî girtin, lê li ser yek kêfî berdewam bike. Pêdivî ye ku ev her gav were kirin dema ku çarçoveya darvekirinê ji bo kodê piştî bendê ne girîng be. Ev di heman demê de pêşniyarek ji MS-ê ye dema ku koda ku dê di pirtûkxaneyek pakkirî de were radest kirin binivîse.

Ka em hinekî bêtir li ser bisekinin ka hûn çawa dikarin li benda qedandina Karekî bisekinin. Li jêr mînakek kodê heye, bi şîroveyên li ser dema ku bendewarî bi şertî baş tête kirin û gava ku ew bi şertî xirab tête kirin.

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
}

Di mînaka yekem de, em li bendê ne ku Task bêyî astengkirina xêza bangê biqede; em ê vegerin ser pêvajoyê tenê gava ku ew jixwe li wir be; heya wê demê, mijara bangê li ser xwe tê hiştin.

Di vebijarka duyemîn de, heya ku encama rêbazê were hesab kirin, em mijara bangê asteng dikin. Ev xirab e ne tenê ji ber ku me mijarek, çavkaniyek wusa hêja ya bernameyê, bi betaliyek hêsan dagir kiriye, lê di heman demê de ji ber ku heke koda rêbaza ku em jê re dibêjin li bendê be, û çarçoweya hevdemkirinê hewce dike ku piştî vegerê li ser mijara bangê. li bendê bin, wê demê em ê xitimandinek bi dest bixin: Mijara gazî li bendê dimîne dema ku encama rêbaza asynkron tê hesab kirin, rêbaza asînkronî bêwate hewl dide ku pêkanîna xwe di mijara bangkirinê de bidomîne.

Dezavantajeke din a vê nêzîkatiyê hilanîna xeletiya tevlihev e. Rastî ev e ku xeletiyên di koda asynchronous de dema ku asynchronic/wait bikar tînin pir hêsan têne xebitandin - ew heman tevdigerin mîna ku kod hevdem be. Dema ku em li ser Peywirek eksorcîzma bendewariya hemdemî bicîh bînin, îstîsna orîjînal vediguhere AggregateException, yanî. Ji bo ku hûn îstîsnayê bi rê ve bibin, hûn ê neçar bin ku celebê InnerException lêkolîn bikin û zincîreyek ger bixwe di hundurê yek bloka girtinê de binivîsin an dema ku têne çêkirin, li şûna zincîra blokên girtinê ku di cîhana C# de naskirî ye, zincîra girtina bikar bînin.

Mînakên sêyem û dawî jî ji ber heman sedemê xirab têne nîşankirin û heman pirsgirêkan di xwe de dihewîne.

Rêbazên WhenAny û WhenAll ji bo li benda komek Karûbaran pir rehet in; ew komek Tasks di nav yek de dipêçin, ku gava yekem Task ji komê were destpêkirin, an jî gava ku hemî pêkanîna xwe qedandin dê bişewite.

Têlên rawestandin

Ji ber sedemên cûda, dibe ku hewce be ku piştî ku herik dest pê kir, were sekinandin. Gelek awayên vê yekê hene. Dersa Thread du rêbazên bi navê minasib hene: Betal kirin и Devjêberdan. Yekem ji bo karanîna pir nayê pêşniyar kirin, ji ber piştî gazîkirina wê di her kêliyek rasthatinî de, di dema hilberandina her fermanekê de, dê îstîsnayek were avêtin ThreadAbortedException. Hûn ne li bendê ne ku îstîsnayek weha were avêtin dema ku guhêrbarek yekjimar zêde dike, rast? Û dema ku vê rêbazê bikar bînin, ev rewşek pir rast e. Heke hûn hewce ne ku CLR di beşek hin kodê de îstîsnayek wusa çêbike, hûn dikarin wê di bangan de bipêçin. Mijar.BeginCriticalRegion, Mijar.EndCriticalRegion. Her kodek ku di bloka dawî de hatî nivîsandin di bangên weha de tê pêçan. Ji ber vê yekê, di kûrahiya koda çarçoveyê de hûn dikarin blokan bi ceribandinek vala bibînin, lê di dawiyê de ne vala. Microsoft vê rêbazê ew qas cesaret dike ku wan ew di navgîniya .net de cîh negirt.

Rêbaza Interrupt bêtir pêşbînîkirî dixebite. Ew dikare bi îstîsnayekê ve têxê qut bike ThreadInterruptedException tenê di wan kêliyên ku têl di rewşek bendewariyê de ye. Dema ku li benda WaitHandle, kilîtkirin, an jî piştî bangkirina Thread.Sleep-ê di daliqandinê de dikeve vê rewşê.

Her du vebijarkên ku li jor hatine destnîşan kirin ji ber nepêşbîniya wan xirab in. Çareserî bikaranîna avahiyek e CancellationToken û çîna CancellationTokenSource. Mesele ev e: mînakek çîna CancellationTokenSource tê afirandin û tenê yê ku xwediyê wê ye dikare bi bangkirina rêbazê operasyonê rawestîne. Bişûndekirin. Tenê CancellationToken ji operasyonê bixwe re derbas dibe. Xwediyên CancellationToken nikarin bi xwe operasyonê betal bikin, lê tenê dikarin kontrol bikin ka operasyon hatiye betal kirin. Ji bo vê taybetmendiyek Boolean heye IsCancellation Daxwaz e û rêbaz ThrowIfCancelRequested. Ya paşîn dê îstîsnayekê bavêje TaskCancelledException heke rêbaza Betalkirinê li ser mînaka CancellationToken ku tê parrot kirin hate gazî kirin. Û ev rêbaza ku ez bi kar tîne pêşniyar dikim. Ev li ser vebijarkên berê çêtirbûnek e ku bi bidestxistina kontrola tam li ser kîjan xalê karek awarte dikare were betal kirin.

Vebijarka herî hovane ji bo rawestandina mijarek ev e ku meriv fonksiyona Win32 API TerminateThread bang bike. Tevgera CLR piştî gazîkirina vê fonksiyonê dibe ku nepêşbînîkirî be. Li ser MSDN li ser vê fonksiyonê jêrîn hatiye nivîsandin: "TerminateThread fonksiyonek xeternak e ku divê tenê di rewşên herî giran de were bikar anîn. "

Veguheztina API-ya mîras bo Bingeha Peywirê bi karanîna rêbaza FromAsync

Ger hûn bextewar in ku hûn li ser projeyek bixebitin ku piştî destpêkirina Tasks hate destpêkirin û ji bo piraniya pêşdebiran tirsa bêdeng rawestiya, wê hingê hûn neçar in ku bi gelek API-yên kevn re, hem yên sêyemîn û hem jî yên tîmê we re mijûl bibin. berê îşkence kiriye. Xwezî, tîmê .NET Framework lênihêrî me kir, her çend belkî armanc ew bû ku em xwe biparêzin. Werhasilî kelam, .NET ji bo veguheztina bê êş koda ku di nêzîkatiyên bernamesaziya asynkron ên kevn de ji ya nû re hatî nivîsandin çend amûr hene. Yek ji wan rêbaza FromAsync ya TaskFactory ye. Di mînaka kodê ya jêrîn de, ez bi karanîna vê rêbazê rêbazên asynok ên kevn ên pola WebRequest di Taskekê de dipêçim.

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

Ev tenê mînakek e û ne mimkûn e ku hûn vê yekê bi celebên çêkirî re bikin, lê her projeyek kevn tenê bi rêbazên BeginDoSomething-ê ku rêbazên IAsyncResult û EndDoSomething ku wê werdigirin vedigerînin, vedigire.

API-ya mîras bi karanîna pola TaskCompletionSource veguhezînin Task Based

Amûrek din a girîng a ku meriv bifikire çîn e TaskCompletionSource. Di warê fonksîyon, armanc û prensîba xebatê de, dibe ku ew hinekî bi rêbaza RegisterWaitForSingleObject ya çîna ThreadPool-ê, ya ku min li jor nivîsand, bîne bîra xwe. Bi karanîna vê polê, hûn dikarin bi hêsanî û bi hêsanî API-yên kevn ên asynkron di Tasks de bipêçin.

Hûn ê bibêjin ku min berê li ser rêbaza FromAsync ya çîna TaskFactory ku ji bo van armancan hatî armanc kirin axivî. Li vir divê em tevahiya dîroka pêşkeftina modelên asynchronous di .net-ê de ku Microsoft di van 15 salên borî de pêşkêşî kiriye, bi bîr bînin: berî Nimûneya Asynchronous-Based a Task (TAP), Nimûneya Bernamesaziya Asynchronous (APP) hebû. li ser rêbazan bû DestpêkirinDoSomething vedigere IAsyncResult û rêbazên DawîTiştek ku wê qebûl dike û ji bo mîrateya van salan, rêbaza FromAsync tenê bêkêmasî ye, lê bi demê re, ew ji hêla Nimûneya Asynchronous Bingeha Bûyerê ve hate guheztin (Û AP), ku tê texmîn kirin ku dema ku operasyona asynchronous qediya dê bûyerek were rakirin.

TaskCompletionSource ji bo pêça Tasks û API-yên mîras ên ku li dora modela bûyerê hatine çêkirin bêkêmasî ye. Esasê xebata wê wiha ye: Tiştek vê polê xwedan taybetmendiyek gelemperî ya celeb Task e, rewşa wê dikare bi rêbazên SetResult, SetException, hwd yên çîna TaskCompletionSource ve were kontrol kirin. Li cîhên ku operatora bendewariyê li ser vê Peywirê hate sepandin, ew ê li gorî rêbaza ku li TaskCompletionSource hatî sepandin bi îstîsnayek ve were darve kirin an têk biçe. Ger hîn jî ne diyar e, em li vê mînaka kodê binêrin, ku hin API-ya EAP-ya kevn di Taskekê de bi karanîna TaskCompletionSource ve hatî pêçandin: dema ku bûyer dişewite, Task dê were veguheztin rewşa Temamkirî, û rêbaza ku operatorê li bendê sepandiye. ji bo vê Peywirê dê pêkanîna wê ji nû ve dest pê bike piştî ku tişt wergirtiye netîce.

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 Serişteyên & Tricks

Vekirina API-yên kevn ne hemî tiştê ku bi karanîna TaskCompletionSource dikare were kirin. Bikaranîna vê polê îmkanek balkêş a sêwirana API-yên cihêreng li ser Karên ku têlan nagirin vedike. Û stream, wekî ku em bîr tînin, çavkaniyek biha ye û hejmara wan bi sînor e (bi piranî ji hêla mîqdara RAM-ê ve). Ev sînorkirin bi hêsanî dikare bi pêşvebirina, mînakî, serîlêdanek webê ya barkirî bi mantiqa karsaziya tevlihev ve were bidestxistin. Werin em îmkanên ku ez behs dikim dema pêkanîna hîleyek wekî Long-Polling bihesibînin.

Bi kurtasî, cewherê hîleyê ev e: hûn hewce ne ku ji API-ê agahdarî li ser hin bûyerên ku li kêleka wê diqewimin bistînin, dema ku API, ji ber hin sedeman, nikare bûyerê ragihîne, lê tenê dikare dewletê vegerîne. Mînaka van hemî API-yên ku berî demên WebSocket-ê li ser HTTP-ê hatine çêkirin an dema ku ji ber hin sedeman ne gengaz bû ku meriv vê teknolojiyê bikar bîne. Xerîdar dikare servera HTTP bipirse. Pêşkêşkara HTTP bixwe nikare pêwendiyê bi xerîdar re bide destpêkirin. Çareseriyek hêsan ev e ku meriv serverê bi karanîna demjimêrek anket bike, lê ev yek barek zêde li ser serverê û derengiyek zêde li ser navînî TimerInterval / 2 diafirîne. server heta ku Demjimêr biqede an bûyerek çêbibe. Ger bûyer qewimî, wê hingê ew tête pêvajo kirin, heke na, wê hingê daxwaz ji nû ve tê şandin.

while(!eventOccures && !timeoutExceeded)  {

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

Lê gava ku hejmara xerîdarên ku li benda bûyerê ne zêde bibe, çareseriyek wusa dê tirsnak be, ji ber ku ... Her xerîdarek wusa li benda bûyerek tevahî mijarek dagir dike. Erê, û dema ku bûyer diqewime em 1ms derengiyek din digirin, pir caran ev ne girîng e, lê çima nermalavê ji ya ku dikare bibe xirabtir dike? Ger em Thread.Sleep(1) ji holê rakin, wê hingê em ê bingehek pêvajoyê 100% bêkar bar bikin, di çerxek bêkêr de bizivire. Bi karanîna TaskCompletionSource hûn dikarin bi hêsanî vê kodê ji nû ve çêbikin û hemî pirsgirêkên ku li jor hatine destnîşan kirin çareser bikin:

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

Ev kod ne amade-hilberînê ye, lê tenê demo ye. Ji bo ku hûn wê di rewşên rastîn de bikar bînin, hûn jî, bi kêmanî, hewce ne ku hûn rewşa ku peyamek digihîje demek ku kes li hêviya wê ne be jî bi rê ve bibe: Di vê rewşê de, divê rêbaza AsseptMessageAsync karek jixwe qedandî vegerîne. Ger ev doza herî gelemperî ye, wê hingê hûn dikarin li ser karanîna ValueTask bifikirin.

Dema ku em daxwaznameyek ji bo peyamekê distînin, em TaskCompletionSource di ferhengê de diafirînin û bi cîh dikin, û dûv re li benda tiştê ku pêşî diqewime: navbera dema diyarkirî bi dawî dibe an peyamek tê wergirtin.

ValueTask: çima û çawa

Operatorên async/li benda, mîna operatorê vegerandina hilberê, ji rêbazê makîneyek dewletê çêdikin, û ev afirandina tiştek nû ye, ku hema hema her gav ne girîng e, lê di rewşên kêm de ew dikare pirsgirêkek çêbike. Ev rewş dibe ku rêbazek ku bi rastî pir caran tê gotin, em li ser deh û sed hezaran bangên di çirkeyê de dipeyivin. Ger rêbazek weha bi rengek wusa were nivîsandin ku di pir rewşan de encamek vedigere û hemî rêbazên bendê derbas dike, wê hingê .NET amûrek ji bo xweşbînkirina vê peyda dike - struktura ValueTask. Ji bo zelalkirina wê, ka em li mînakek karanîna wê binêrin: cacheyek heye ku em pir caran diçin. Di wê de hin nirx hene û dûv re em wan bi hêsanî vedigerînin; heke na, wê hingê em diçin hin IO hêdî da ku wan bistînin. Ez dixwazim ya paşîn bi asynchronous bikim, ku tê vê wateyê ku hemî rêbaz asynchronous dibe. Ji ber vê yekê, awayê eşkere ya nivîsandina rêbazê wiha ye:

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

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

Ji ber ku xwestina ku meriv hinekî xweşbîn bike, û tirsek piçûk ji tiştê ku Roslyn dê di berhevkirina vê kodê de çêbike, hûn dikarin vê nimûneyê wekî jêrîn ji nû ve binivîsin:

public Task<string> GetById(int id) {

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

Bi rastî, di vê rewşê de çareseriya çêtirîn dê xweşbînkirina riya germ be, ango, wergirtina nirxek ji ferhengê bêyî veqetandinên nehewce û barkirina GC-yê, di heman demê de di wan rewşên kêm de dema ku em hîn jî hewce ne ku ji bo daneyan biçin IO. , her tişt dê bi awayê kevn plus / kêm bimîne:

public ValueTask<string> GetById(int id) {

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

Ka em ji nêz ve li vê perçeya kodê mêze bikin: heke di cache de nirxek hebe, em avahiyek diafirînin, wekî din dê peywira rastîn di yek watedar de were pêçandin. Koda gazîkirinê ferq nake ku ev kod di kîjan rê de hatî darve kirin: ValueTask, ji hêla hevoksaziya C# ve, dê di vê rewşê de mîna Karek birêkûpêk tevbigere.

TaskSchedulers: birêvebirina stratejiyên destpêkirina peywirê

API-ya paşîn a ku ez dixwazim bifikirim pola ye TaskScheduler û jêderkên wê. Min berê li jor behs kir ku TPL xwedan şiyana birêvebirina stratejiyên ji bo belavkirina Peywiran li ser mijaran e. Stratejiyên weha di neviyên çîna TaskScheduler de têne diyar kirin. Hema hema her stratejiyek ku hûn hewce ne dikarin di pirtûkxaneyê de werin dîtin. ParallelExtensionsExtras, ji hêla Microsoft ve hatî pêşve xistin, lê ne beşek ji .NET-ê ye, lê wekî pakêtek Nuget tê peyda kirin. Ka em bi kurtî li hinek ji wan binêrin:

  • CurrentThreadTaskScheduler - Karên li ser mijara heyî pêk tîne
  • LimitedConcurrencyLevelTaskScheduler - Hejmara Karên ku bi hevdemî têne darve kirin bi parametreya N-ya ku di çêker de tê pejirandin sînordar dike
  • OrderedTaskScheduler - wekî LimitedConcurrencyLevelTaskScheduler(1) tê pênase kirin, ji ber vê yekê peywir dê bi rêzê bêne darve kirin.
  • WorkStealingTaskScheduler - pêk tîne kar-dizî nêzîkatiya belavkirina peywirê. Di bingeh de ew ThreadPoolek veqetandî ye. Pirsgirêka ku di .NET ThreadPool de çînek statîk e, yek ji bo hemî serlêdanan çareser dike, ku tê vê wateyê ku bargiraniya wê an nerast karanîna wê di beşek bernameyê de dikare bibe sedema bandorên alî li yekî din. Wekî din, pir dijwar e ku meriv sedema kêmasiyên weha fam bike. Va. Dibe ku hewcedarî bi karanîna WorkStealingTaskSchedulerên cihêreng di beşên bernameyê de hebe ku karanîna ThreadPool dibe ku êrîşkar û nepêşbînbar be.
  • QueuedTaskScheduler - destûrê dide te ku hûn karan li gorî rêzikên rêza pêşîn pêk bînin
  • ThreadPerTaskScheduler - Ji bo her Peywirek ku li ser wê tê meşandin mijarek cûda diafirîne. Dikare ji bo karên ku ji bo qedandina demek dirêj a nediyar digire kêrhatî be.

Kêmasiyek baş heye gotara li ser TaskSchedulers li ser bloga microsoft.

Ji bo verastkirina hêsan a her tiştê ku bi Tasks ve girêdayî ye, Visual Studio pencereyek Tasks heye. Di vê pencereyê de hûn dikarin rewşa heyî ya peywirê bibînin û biçin ser rêzika kodê ya ku niha tê xebitandin.

.NET: Amûrên ji bo xebitandina bi multithreading û asynchrony. Beş 1

PLinq û çîna Parallel

Ji bilî Tasks û her tiştê ku li ser wan hatî gotin, di .NET-ê de du amûrên din ên balkêş hene: PLinq (Linq2Parallel) û çîna Parallel. Yekem soza pêkanîna paralel a hemî operasyonên Linq li ser gelek mijaran dide. Hejmara mijaran bi karanîna rêbaza dirêjkirina WithDegreeOfParallelism dikare were mîheng kirin. Mixabin, pir caran PLinq di moda xweya xwerû de di derheqê hundurê çavkaniya daneya we de agahdariya têr nake ku lezek girîng peyda bike, ji hêla din ve, lêçûna ceribandinê pir kêm e: hûn tenê hewce ne ku berê rêbaza AsParallel bang bikin. zincîra rêbazên Linq û ceribandinên performansê dimeşînin. Digel vê yekê, mimkun e ku hûn bi karanîna mekanîzmaya Parçeyan di derheqê cewhera çavkaniya daneya xwe de agahdariya zêde ji PLinq re derbas bikin. Hûn dikarin bêtir bixwînin vir и vir.

Çîna statîk Parallel ji bo dubarekirina bi berhevokek Foreach bi paralel, pêkanîna loopek For, û pêkanîna çend delegeyan di Invoke paralel de rêbazan peyda dike. Pêkanîna kêşeya heyî dê were sekinandin heya ku hesab neqede. Bi derbaskirina ParallelOptions wekî argumana dawîn dikare hejmara mijaran were mîheng kirin. Her weha hûn dikarin bi karanîna vebijarkan TaskScheduler û CancellationToken diyar bikin.

vebiguherin

Dema ku min dest bi nivîsandina vê gotarê kir li ser materyalên rapora xwe û agahdariya ku min di dema xebata xwe de piştî wê berhev kir, min texmîn nedikir ku ew qas zêde hebe. Naha, gava ku edîtorê nivîsê yê ku ez vê gotarê tê de bi şermezarî dinivîsim ji min re bêje ku rûpela 15 çûye, ez ê encamên navberê bi kurtahî bikim. Dê hîleyên din, API, amûrên dîtbar û xeletiyên din di gotara pêş de werin vegirtin.

Encamên

  • Ji bo ku hûn çavkaniyên PC-yên nûjen bikar bînin pêdivî ye ku hûn amûrên ji bo xebata bi têlan, asynkronî û paralelîzmê zanibin.
  • .NET ji bo van armancan gelek amûrên cûda hene
  • Hemî wan bi yek carî xuya nebûn, ji ber vê yekê hûn pir caran dikarin yên mîras bibînin, lêbelê, awayên guheztina API-yên kevn bêyî hewildan hene.
  • Di .NET-ê de bi têlan re dixebitin ji hêla dersên Thread û ThreadPool ve têne temsîl kirin
  • Rêbazên Thread.Abort, Thread.Interrupt, û Win32 API TerminateThread xeternak in û ji bo karanîna nayê pêşniyar kirin. Di şûna wê de, çêtir e ku meriv mekanîzmaya CancellationToken bikar bîne
  • Herikîn çavkaniyek hêja ye û peydakirina wê kêm e. Divê ji rewşên ku mijarên li benda bûyeran mijûl in, werin dûrxistin. Ji bo vê yekê hêsan e ku meriv çîna TaskCompletionSource bikar bîne
  • Amûrên .NET yên herî bi hêz û pêşkeftî yên ji bo xebata bi paralelîsm û asynkroniyê Tasks in.
  • Operatorên c# async/bendî têgeha bendewariya ne-astengkirinê pêk tînin
  • Hûn dikarin bi karanîna dersên ji TaskScheduler-dervekirî belavkirina Peywiran li ser mijaran kontrol bikin.
  • Struktura ValueTask dikare di xweşbînkirina rêyên germ û trafîka bîranînê de kêrhatî be
  • Paceyên Tasks û Mijarên Visual Studio gelek agahdariya kêrhatî ji bo verastkirina koda pir-mijal an asynchronous peyda dikin.
  • PLinq amûrek xweş e, lê dibe ku di derheqê çavkaniya daneya we de agahdariya têr nebe, lê ev dikare bi karanîna mekanîzmaya dabeşkirinê were rast kirin
  • Ez bêtir ji te hez dikim…

Source: www.habr.com

Add a comment