.NET: Alat pikeun gawé bareng multithreading na Asynchrony. Bagian 1

Kuring medarkeun artikel asli ngeunaan Habr, tarjamahan anu dipasang dina perusahaan postingan blog.

Kabutuhan pikeun ngalakukeun hiji hal asynchronously, tanpa nungguan hasilna di dieu jeung ayeuna, atawa ngabagi karya badag diantara sababaraha unit ngajalankeun eta, aya saméméh mecenghulna komputer. Kalayan datangna aranjeunna, kabutuhan ieu janten nyata pisan. Ayeuna, dina taun 2019, kuring ngetik tulisan ieu dina laptop nganggo prosésor Intel Core 8-inti, dimana langkung ti saratus prosés dijalankeun paralel, sareng langkung seueur benang. Di caket dieu, aya telepon anu rada kumuh, ngagaleuh sababaraha taun ka pengker, éta ngagaduhan prosésor 8-inti. Sumber daya tematik pinuh ku tulisan sareng pidéo dimana pangarangna muji smartphone unggulan taun ieu anu ngagaduhan prosesor 16-inti. MS Azure nyadiakeun mesin virtual kalawan 20 processor inti jeung 128 TB RAM pikeun kirang ti $ 2 / jam. Hanjakalna, mustahil pikeun nimba maksimal sareng nganggo kakuatan ieu tanpa tiasa ngatur interaksi benang.

Terminologi

Prosés - Obyék OS, spasi alamat terasing, ngandung threads.
Benang - hiji obyék OS, unit pangleutikna palaksanaan, bagian tina prosés, threads babagi memori sareng sumber sejenna diantara sorangan dina prosés.
Multitasking - OS milik, kamampuhan pikeun ngajalankeun sababaraha prosés sakaligus
Multi-inti - milik processor, kamampuhan pikeun ngagunakeun sababaraha cores pikeun ngolah data
Multiprocessing - milik hiji komputer, kamampuhan pikeun sakaligus gawé bareng sababaraha prosesor fisik
Multithreading - sipat prosés, kamampuhan pikeun ngadistribusikaeun processing data diantara sababaraha threads.
Paralélisme - ngalakukeun sababaraha tindakan sacara fisik sakaligus per unit waktos
Asynchrony - palaksanaan operasi tanpa ngantosan parantosan pamrosésan ieu; hasil palaksanaan tiasa diolah engké.

Métafora

Henteu sadayana definisi anu saé sareng sababaraha peryogi katerangan tambahan, janten kuring bakal nambihan métafora ngeunaan masak sarapan kana terminologi anu diwanohkeun sacara resmi. Masak sarapan dina métafora ieu mangrupa prosés.

Nalika nyiapkeun sarapan isuk-isuk kuring (CPU) Abdi ka dapur (komputer). Abdi gaduh 2 tangan (cores). Aya sababaraha alat di dapur (IO): oven, ketel, alat keur manggang roti, kulkas. Kuring hurungkeun gas, nempatkeun panci sareng tuang minyak kana éta tanpa ngantosan panas (asynchronously, Non-Meungpeuk-IO-Antosan), Kuring ngaluarkeun endog tina kulkas sareng meupeuskeun kana piring, teras ngéléhkeun ku hiji leungeun (Thread #1), jeung kadua (Thread #2) nyekel piring (Sumber Dibagikeun). Ayeuna abdi hoyong ngahurungkeun ketel, tapi abdi teu gaduh cukup leungeun (Kalaparan benang) Antukna, pan frying panas (Ngolah hasilna) kana nu kuring tuang naon geus dikocok. Kuring ngahontal ketel teras hurungkeun sareng bodo ningali cai ngagolak di jerona (Blocking-IO-Antosan), najan salila ieu manéhna bisa nyeuseuh piring tempat manéhna mecut omelet.

Kuring masak omelette ngan ukur nganggo 2 leungeun, sareng kuring henteu gaduh deui, tapi dina waktos anu sami, dina waktos sebat omelette, 3 operasi dilaksanakeun sakaligus: sebat omelette, nahan piring, pemanasan pan frying. CPU mangrupa bagian panggancangna tina komputer, IO nyaeta naon paling mindeng sagalana slows turun, jadi mindeng hiji solusi éféktif mangrupa nempatan CPU jeung hal bari narima data ti IO.

Nuluykeun métafora:

  • Upami dina prosés nyiapkeun omelet, kuring ogé bakal nyobian ngarobih baju, ieu bakal janten conto multitasking. Nuansa penting: komputer langkung saé tibatan jalma.
  • Dapur sareng sababaraha koki, contona di réstoran - komputer multi-inti.
  • Seueur réstoran di food court di pusat balanja - pusat data

.NET Pakakas

.NET téh alus dina gawé bareng threads, sakumaha kalayan loba hal séjén. Kalawan unggal versi anyar, éta ngawanohkeun beuki loba parabot anyar pikeun gawé bareng aranjeunna, lapisan anyar abstraksi leuwih threads OS. Nalika damel sareng pangwangunan abstraksi, pamekar kerangka ngagunakeun pendekatan anu nyéépkeun kasempetan, nalika nganggo abstraksi tingkat luhur, pikeun turun hiji atanapi langkung tingkat di handap. Paling sering ieu mah teu perlu, dina kanyataanana eta muka panto pikeun shooting diri dina suku jeung shotgun a, tapi kadang, dina kasus nu jarang dipake, meureun nya hiji-hijina jalan pikeun ngajawab masalah nu teu direngsekeun dina tingkat abstraksi ayeuna. .

Ku alat, abdi hartosna duanana interfaces programming aplikasi (API) disadiakeun ku kerangka jeung bungkusan pihak-katilu, kitu ogé solusi software sakabeh nu simplify pilarian pikeun sagala masalah nu patali jeung kode multi-threaded.

Ngamimitian thread a

Kelas Thread nyaéta kelas paling dasar dina .NET pikeun gawé bareng threads. Konstruktor nampi salah sahiji tina dua utusan:

  • ThreadStart - Taya parameter
  • ParametrizedThreadStart - kalawan hiji parameter tipe objék.

Delegasi bakal dieksekusi dina thread nu karek dijieun sanggeus nelepon metoda Start.Mun delegasi tipe ParametrizedThreadStart dibikeun ka constructor nu, lajeng hiji obyék kudu dibikeun ka métode Start. Mékanisme ieu diperyogikeun pikeun mindahkeun inpormasi lokal kana aliran. Eta sia noting yén nyieun thread mangrupa operasi mahal, sarta thread sorangan mangrupa obyék beurat, sahenteuna sabab allocates 1MB memori dina tumpukan sarta merlukeun interaksi jeung API OS.

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

Kelas ThreadPool ngagambarkeun konsép kolam renang. Dina .NET, thread pool mangrupakeun sapotong rékayasa, sarta pamekar di Microsoft geus nempatkeun loba usaha pikeun mastikeun gawéna optimal dina rupa-rupa skenario.

Konsep umum:

Ti mimiti aplikasi dimimitian, éta nyiptakeun sababaraha utas dina cadangan di latar tukang sareng nyayogikeun kamampuan pikeun dianggo. Upami benang sering dianggo sareng jumlahna ageung, kolam renang ngalegaan pikeun nyumponan kabutuhan panelepon. Nalika henteu aya benang bébas dina kolam renang dina waktos anu pas, éta bakal ngantosan salah sahiji benang uih deui, atanapi nyiptakeun anu énggal. Ieu nuturkeun yén kolam renang thread anu hadé pikeun sababaraha lampah jangka pondok tur kirang cocog pikeun operasi anu ngajalankeun sakumaha jasa sapanjang sakabéh operasi aplikasi.

Pikeun nganggo benang tina kolam renang, aya metode QueueUserWorkItem anu nampi utusan tina tipe WaitCallback, anu gaduh tanda tangan anu sami sareng ParametrizedThreadStart, sareng parameter anu disalurkeun ka éta ngalaksanakeun fungsi anu sami.

ThreadPool.QueueUserWorkItem(...);

Metodeu thread pool anu kirang dikenal RegisterWaitForSingleObject dianggo pikeun ngatur operasi IO anu henteu meungpeuk. Delegasi anu lulus kana metode ieu bakal disebut nalika WaitHandle dialihkeun kana metodena "Dileupaskeun".

ThreadPool.RegisterWaitForSingleObject(...)

.NET boga timer thread na eta béda ti WinForms / WPF timers nu Handler na bakal disebut dina thread dicokot tina kolam renang nu.

System.Threading.Timer

Aya ogé cara anu rada aheng pikeun ngirim utusan pikeun dieksekusi ka benang tina kolam renang - metodeu BeginInvoke.

DelegateInstance.BeginInvoke

Abdi hoyong sakeudeung cicing dina fungsi anu seueur metode di luhur tiasa disebat - CreateThread ti Kernel32.dll Win32 API. Aya jalan, hatur nuhun kana mékanisme métode extern, mun nelepon fungsi ieu. Kuring geus katempo panggero misalna ngan sakali dina conto dahsyat kode warisan, sarta motivasi pangarang anu ngalakukeun persis ieu masih tetep misteri keur kuring.

Kernel32.dll CreateThread

Nempo jeung Debugging Threads

Threads dijieun ku anjeun, sadaya komponén pihak-katilu, jeung .NET kolam renang bisa ditempo dina jandela Threads of Visual Studio. Jandéla ieu ngan bakal nampilkeun inpormasi benang nalika aplikasi dina kaayaan debug sareng dina modeu Break. Di dieu anjeun bisa gampang nempo ngaran tumpukan jeung prioritas unggal thread, sarta pindah debugging ka thread husus. Nganggo milik Prioritas kelas Thread, anjeun tiasa nyetél prioritas benang, anu bakal ditanggap ku OC sareng CLR salaku rekomendasi nalika ngabagi waktos prosesor antara benang.

.NET: Alat pikeun gawé bareng multithreading na Asynchrony. Bagian 1

Perpustakaan Paralel Tugas

Tugas Perpustakaan Paralel (TPL) diwanohkeun dina .NET 4.0. Ayeuna éta standar sareng alat utama pikeun damel sareng asynchrony. Sakur kode anu ngagunakeun pendekatan anu langkung lami dianggap warisan. Unit dasar TPL nyaéta kelas Tugas ti System.Threading.Tasks namespace. Tugas mangrupa abstraksi leuwih thread a. Kalayan versi anyar tina basa C #, kami ngagaduhan cara anu elegan pikeun damel sareng Tugas - operator async / await. Konsep ieu ngamungkinkeun pikeun nulis kode Asynchronous saolah-olah éta basajan tur sinkron, ieu ngamungkinkeun malah pikeun jalma kalawan saeutik pamahaman cara internal tina threads nulis aplikasi nu make eta, aplikasi nu teu freeze nalika ngajalankeun operasi panjang. Nganggo async/await mangrupikeun topik pikeun hiji atanapi malah sababaraha tulisan, tapi kuring bakal nyobian ngartos inti dina sababaraha kalimat:

  • async mangrupakeun modifier sahiji metodeu balik Tugas atawa batal
  • sarta await mangrupakeun non-blocking Tugas operator ngantosan.

Sakali deui: operator await, dina kasus umum (aya pengecualian), bakal ngaleupaskeun thread ayeuna palaksanaan salajengna, sarta nalika Tugas rengse palaksanaan na, sarta thread (dina kanyataanana, éta bakal leuwih bener ngomong konteks. , tapi nu langkung lengkep ihwal nu engké) bakal neruskeun executing metoda salajengna. Jero .NET, mékanisme ieu dilaksanakeun dina cara nu sarua salaku ngahasilkeun balik, nalika métode ditulis robah jadi sakabeh kelas, nu mangrupakeun mesin kaayaan sarta bisa dieksekusi dina potongan misah gumantung kana kaayaan ieu. Saha kabetot bisa nulis sagala kode basajan maké asynс / await, compile sarta nempo assembly maké JetBrains dotPeek kalawan Kompiler dihasilkeun Code diaktipkeun.

Hayu urang tingali pilihan pikeun ngaluncurkeun sareng nganggo Tugas. Dina conto kode di handap, urang nyieun tugas anyar nu teu nanaon mangpaat (Thread.Sare(10000)), Tapi dina kahirupan nyata ieu kedah sababaraha karya CPU-intensif kompléks.

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
}

Tugas dijieun kalayan sababaraha pilihan:

  • LongRunning mangrupikeun petunjuk yén tugasna moal réngsé gancang, anu hartosna panginten henteu kedah nyandak benang tina kolam renang, tapi nyiptakeun anu misah pikeun Tugas ieu supados henteu ngarugikeun batur.
  • AttachedToParent - Tugas tiasa disusun dina hirarki. Upami pilihan ieu dianggo, maka Tugas tiasa aya dina kaayaan dimana éta parantos réngsé sareng ngantosan palaksanaan anak-anakna.
  • PreferFairness - hartosna langkung saé ngalaksanakeun Tugas anu dikirim pikeun dieksekusi sateuacanna sateuacan dikirim engké. Tapi ieu ngan ukur rekomendasi sareng hasilna henteu dijamin.

Parameter kadua disalurkeun kana métode nyaéta CancellationToken. Pikeun leres nanganan pembatalan hiji operasi sanggeus eta geus dimimitian, kode nu keur dieksekusi kudu ngeusi cék pikeun kaayaan CancellationToken. Upami teu aya cék, maka metode Bolay anu disebut dina obyek CancellationTokenSource bakal tiasa ngeureunkeun palaksanaan Tugas ngan sateuacan dimimitian.

Parameter panungtungan nyaéta objék scheduler tipe TaskScheduler. Kelas ieu sareng turunanna dirancang pikeun ngatur strategi pikeun ngadistribusikaeun Tugas dina utas; sacara standar, Tugas bakal dieksekusi dina benang acak tina kolam renang.

Operator await diterapkeun kana Tugas anu diciptakeun, anu hartosna kode anu ditulis saatosna, upami aya, bakal dieksekusi dina kontéks anu sami (sering ieu hartosna dina benang anu sami) salaku kode sateuacan ngantosan.

Metoda ieu ditandaan salaku batal async, nu hartina bisa ngagunakeun operator await, tapi kode nelepon moal bisa ngadagoan palaksanaan. Upami fitur sapertos kitu diperyogikeun, maka metodena kedah uih deui Tugas. Métode anu ditandaan batal async cukup umum: biasana, ieu mangrupikeun pawang acara atanapi metode sanés anu tiasa dianggo dina prinsip seuneu sareng hilap. Upami anjeun kedah henteu ngan ukur masihan kasempetan pikeun ngantosan dugi ka ahir palaksanaan, tapi ogé ngabalikeun hasilna, maka anjeun kedah nganggo Tugas.

Dina Tugas anu metodeu StartNew dipulangkeun, kitu ogé anu sanés, anjeun tiasa nyauran metode ConfigureAwait sareng parameter palsu, teras palaksanaan saatos ngantosan bakal diteruskeun sanés dina kontéks anu direbut, tapi dina anu sawenang. Ieu kedah salawasna dilakukeun nalika konteks palaksanaan henteu penting pikeun kode sanggeus await. Ieu oge rekomendasi ti MS nalika nulis kode anu bakal dikirimkeun rangkep dina perpustakaan.

Hayu urang cicing sakedik ngeunaan kumaha anjeun tiasa ngantosan parantosan Tugas. Di handap ieu conto kode, kalawan komentar on nalika ekspektasi dipigawé conditionally ogé sarta lamun eta dipigawé conditionally kirang.

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
}

Dina conto kahiji, urang ngadagoan Tugas pikeun ngarengsekeun tanpa blocking thread nélépon; urang bakal mulang ka ngolah hasilna ngan lamun eta geus aya; nepi ka harita, thread nélépon ditinggalkeun ka alat sorangan.

Dina pilihan kadua, urang meungpeuk thread nelepon nepi ka hasil tina metoda diitung. Ieu goréng henteu ngan kusabab urang geus nempatan thread, misalna sumberdaya berharga tina program, kalawan idleness basajan, tapi ogé sabab lamun kode sahiji metodeu nu urang nelepon ngandung await, sarta kontéks sinkronisasi merlukeun balik deui ka thread nelepon sanggeus. await, lajeng urang bakal meunang deadlock a: Thread nélépon ngantosan hasil tina metoda Asynchronous diitung, metoda Asynchronous ngusahakeun sia neruskeun palaksanaan na dina thread nélépon.

Karugian anu sanés tina pendekatan ieu nyaéta penanganan kasalahan anu rumit. Kanyataan yén kasalahan dina kode Asynchronous nalika maké async / ngantosan pisan gampang pikeun nanganan - aranjeunna kalakuanana sarua lamun kode éta sinkron. Sedengkeun lamun urang nerapkeun sinkron antosan exorcism a Tugas, iwal aslina robah jadi AggregateException, i.e. Pikeun nanganan iwal, anjeun kudu nalungtik jenis InnerException jeung nulis hiji lamun ranté diri di jero hiji blok nyekel atanapi nganggo nyekel nalika ngawangun, tinimbang ranté tina blok nyekel anu leuwih akrab dina C # dunya.

Conto katilu sareng terakhir ogé ditandaan goréng pikeun alesan anu sami sareng ngandung sadaya masalah anu sami.

Métode WhenAny sareng WhenAll pisan merenah pikeun ngantosan sakelompok Tugas; aranjeunna mungkus sakelompok Tugas janten hiji, anu bakal hurung nalika Tugas ti grup mimiti dipicu, atanapi nalika sadayana parantos réngsé palaksanaan.

Ngeureunkeun benang

Pikeun sagala rupa alesan, meureun perlu pikeun ngeureunkeun aliran sanggeus dimimitian. Aya sababaraha cara pikeun ngalakukeun ieu. Kelas Thread gaduh dua metode anu leres namina: Aborsi и Ngaganggu. Anu kahiji henteu disarankeun pikeun dianggo, sabab sanggeus nelepon eta iraha wae momen acak, salila ngolah sagala instruksi, iwal bakal dialungkeun ThreadAbortedException. Anjeun teu nyangka iwal sapertos bakal dialungkeun nalika incrementing sagala variabel integer, katuhu? Sareng nalika nganggo metode ieu, ieu mangrupikeun kaayaan anu nyata. Upami anjeun kedah nyegah CLR tina ngahasilkeun pengecualian sapertos dina bagian kode anu tangtu, anjeun tiasa mungkus dina telepon. Thread.BeginCriticalRegion, Thread.EndCriticalRegion. Sakur kode anu ditulis dina blok tungtungna dibungkus dina telepon sapertos kitu. Ku sabab kitu, dina jero kode kerangka anjeun tiasa mendakan blok kalayan usaha kosong, tapi tungtungna henteu kosong. Microsoft ngalepatkeun metode ieu dugi ka aranjeunna henteu kalebet kana inti .net.

Metoda interupsi jalan leuwih predictably. Bisa ngaganggu thread kalawan iwal ThreadInterruptedException ngan dina mangsa éta nalika benang dina kaayaan ngantosan. Éta asup kana kaayaan ieu bari ngagantung bari ngantosan WaitHandle, konci, atanapi saatos nelepon Thread.Sleep.

Kadua pilihan anu dijelaskeun di luhur goréng kusabab teu kaduga. Solusina nyaéta ngagunakeun struktur CancellationToken jeung kelas CancellationTokenSource. Intina nyaéta kieu: conto kelas CancellationTokenSource didamel sareng ngan hiji anu gaduhna tiasa ngeureunkeun operasi ku cara nyauran metodeu. ngabatalkeun. Ngan CancellationToken disalurkeun kana operasi sorangan. Pamilik CancellationToken teu bisa ngabolaykeun operasi sorangan, tapi ngan bisa pariksa naha operasi geus dibatalkeun. Aya sipat Boolean pikeun ieu IsCancellationRequested jeung métode ThrowIfCancelRequested. Panungtungan bakal buang iwal TaskCancelledException lamun metoda Cancel disebut dina conto CancellationToken keur parroted. Sareng ieu mangrupikeun metode anu kuring nyarankeun ngagunakeun. Ieu mangrupikeun paningkatan tina pilihan anu saacanna ku kéngingkeun kontrol pinuh dina waktos dimana operasi pengecualian tiasa dibatalkeun.

Pilihan anu paling brutal pikeun ngeureunkeun benang nyaéta nyauran fungsi Win32 API TerminateThread. Paripolah CLR sanggeus nelepon pungsi ieu bisa jadi unpredictable. Dina MSDN di handap ieu ditulis ngeunaan fungsi ieu: "TerminateThread mangrupikeun fungsi anu bahaya anu ngan ukur dianggo dina kasus anu paling ekstrim. “

Ngarobih API warisan kana Tugas Dumasar nganggo metode FromAsync

Upami anjeun cukup untung pikeun ngerjakeun proyék anu dimimitian saatos Tugas diwanohkeun sareng lirén nyababkeun horor sepi pikeun sabagéan ageung pamekar, maka anjeun henteu kedah ngurus seueur API anu lami, boh pihak katilu sareng tim anjeun. geus disiksa di jaman baheula. Kabeneran, tim .NET Framework ngurus kami, sanajan meureun tujuanana pikeun ngajaga diri. Sanajan kitu, .NET boga sababaraha parabot pikeun painlessly ngarobah kodeu ditulis dina programming Asynchronous heubeul ngadeukeutan ka nu anyar. Salah sahijina nyaéta metode FromAsync tina TaskFactory. Dina conto kode di handap, kuring mungkus métode async heubeul tina kelas WebRequest dina Tugas ngagunakeun métode ieu.

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

Ieu ngan hiji conto jeung anjeun saperti teu mirip kudu ngalakukeun ieu jeung tipe diwangun-di, tapi sagala proyék heubeul saukur teeming jeung métode BeginDoSomething nu balik IAsyncResult jeung métode EndDoSomething nu narima eta.

Ngarobih API warisan kana Tugas Dumasar nganggo kelas TaskCompletionSource

Alat penting séjén pikeun mertimbangkeun nyaéta kelas Sumber Tugas. Dina watesan fungsi, tujuan jeung prinsip operasi, meureun nya rada reminiscent tina RegisterWaitForSingleObject metoda kelas ThreadPool, nu kuring wrote ngeunaan luhur. Ngagunakeun kelas ieu, anjeun bisa kalayan gampang tur merenah mungkus API Asynchronous heubeul dina Tugas.

Anjeun bakal nyarios yén kuring parantos nyarios ngeunaan metode FromAsync tina kelas TaskFactory anu dimaksudkeun pikeun tujuan ieu. Di dieu urang kudu nginget sakabéh sajarah ngembangkeun model Asynchronous di .net nu Microsoft geus ditawarkeun dina 15 taun kaliwat: saméméh Task-Based Asynchronous Pattern (TAP), aya Asynchronous Programming Pattern (APP), nu éta ngeunaan métode MimitiDoSomething balik IAsyncResult jeung métode TungtungDoSomething anu nampi éta sareng pikeun warisan taun-taun ieu metode FromAsync ngan sampurna, tapi kana waktosna, éta diganti ku Pola Asynchronous Based Event (Jeung AP), anu nganggap yén hiji acara bakal diangkat nalika operasi Asynchronous réngsé.

TaskCompletionSource sampurna pikeun ngabungkus Tugas sareng API warisan anu diwangun dina modél acara. Hakekat karyana nyaéta kieu: hiji obyék kelas ieu boga milik umum tina tipe Tugas, kaayaan nu bisa dikawasa ngaliwatan métode SetResult, SetException, jsb tina kelas TaskCompletionSource. Di tempat dimana operator await diterapkeun kana Tugas ieu, éta bakal dieksekusi atanapi gagal kalayan pengecualian gumantung kana metode anu dilarapkeun kana TaskCompletionSource. Upami éta masih henteu écés, hayu urang tingali conto kode ieu, dimana sababaraha API EAP lami dibungkus dina Tugas nganggo TaskCompletionSource: nalika kajadian kahuruan, Tugas bakal dialihkeun ka kaayaan Réngsé, sareng metode anu nerapkeun operator await. ka Tugas ieu bakal neruskeun palaksanaan na sanggeus narima obyék hasil.

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 & Trik

Bungkus API anu lami sanés sadayana anu tiasa dilakukeun nganggo TaskCompletionSource. Ngagunakeun kelas ieu muka hiji kamungkinan metot ngarancang rupa API on Tugas nu teu dikawasaan ku threads. Sareng aliran, sakumaha anu urang émut, mangrupikeun sumber anu mahal sareng jumlahna terbatas (utamana ku jumlah RAM). Watesan ieu tiasa gampang dihontal ku cara ngembangkeun, contona, aplikasi wéb anu dimuat sareng logika bisnis anu rumit. Hayu urang nganggap kemungkinan anu kuring nyarioskeun nalika ngalaksanakeun trik sapertos Long-Polling.

Pondokna, hakekat trik ieu: anjeun kedah nampi inpormasi ti API ngeunaan sababaraha kajadian anu aya di sisina, sedengkeun API, pikeun sababaraha alesan, henteu tiasa ngalaporkeun kajadian, tapi ngan ukur tiasa uih deui nagara. Conto ieu sadayana API anu diwangun dina luhureun HTTP sateuacan jaman WebSocket atanapi nalika teu mungkin pikeun sababaraha alesan pikeun ngagunakeun téknologi ieu. Klién tiasa naroskeun ka server HTTP. The HTTP server teu bisa sorangan initiate komunikasi jeung klien nu. Hiji leyuran basajan nyaéta polling server ngagunakeun timer a, tapi ieu nyiptakeun beban tambahan dina server na hiji reureuh tambahan rata-rata TimerInterval / 2. Pikeun meunang sabudeureun ieu, trik disebut Long Polling ieu nimukeun, nu ngalibatkeun delaying respon ti. server nepi ka Timeout kadaluwarsa atawa hiji kajadian bakal lumangsung. Upami acara parantos kajantenan, teras diolah, upami henteu, pamundut dikirim deui.

while(!eventOccures && !timeoutExceeded)  {

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

Tapi solusi sapertos kitu bakal kabuktian dahsyat pas jumlah klien ngantosan acara naek, sabab ... Tiap klien sapertos nempatan hiji sakabéh thread ngantosan hiji acara. Sumuhun, sarta kami meunang tambahan 1ms reureuh nalika acara ieu dipicu, paling sering ieu teu signifikan, tapi naha nyieun software nu leuwih goreng ti eta tiasa? Lamun urang miceun Thread.Sleep(1), sia-sia urang bakal ngamuat hiji inti processor 100% dianggurkeun, puteran dina siklus gunana. Nganggo TaskCompletionSource anjeun tiasa ngadamel deui kode ieu sareng ngarengsekeun sadaya masalah anu diidentifikasi di luhur:

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

Kode ieu teu produksi-siap, tapi ngan demo. Pikeun ngagunakeun éta dina kasus nyata, anjeun ogé kedah, sahenteuna, pikeun nanganan kaayaan nalika pesen sumping dina waktos anu teu aya anu ngarepkeunana: dina hal ieu, metode AsseptMessageAsync kedah uih deui Tugas anu parantos réngsé. Upami ieu mangrupikeun kasus anu paling umum, maka anjeun tiasa mikirkeun ngagunakeun ValueTask.

Nalika kami nampi pamenta pikeun pesen, kami nyiptakeun sareng nempatkeun TaskCompletionSource dina kamus, teras ngantosan naon anu mimiti: interval waktos anu ditangtukeun kadaluwarsa atanapi pesen anu ditampi.

ValueTask: naha jeung kumaha

Operator async / await, kawas operator balik ngahasilkeun, ngahasilkeun mesin kaayaan tina métode, sarta ieu kreasi hiji objek anyar, nu ampir sok teu penting, tapi dina kasus nu jarang dipake bisa nyieun masalah. Kasus ieu bisa jadi métode anu disebut bener mindeng, urang ngobrol ngeunaan puluhan sarta ratusan rébu telepon per detik. Lamun metoda saperti ieu ditulis dina cara sapertos nu di hal nu ilahar eta mulih hasil bypassing sadaya métode await, teras .NET nyadiakeun alat pikeun ngaoptimalkeun ieu - struktur ValueTask. Sangkan jelas, hayu urang nempo conto pamakéan na: aya hiji cache nu urang buka pisan sering. Aya sababaraha nilai di jerona teras urang ngan ukur mulangkeunana; upami henteu, maka urang angkat ka sababaraha IO anu laun pikeun nyandakana. Abdi hoyong ngalakukeun dimungkinkeun asynchronously, nu hartina sakabéh métode tétéla jadi Asynchronous. Ku kituna, cara atra nulis métode nyaéta kieu:

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

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

Kusabab kahayang pikeun ngaoptimalkeun saeutik, sarta saeutik sieun naon Roslyn bakal ngahasilkeun nalika compile kode ieu, anjeun bisa nulis balik conto kieu:

public Task<string> GetById(int id) {

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

Mémang, solusi optimal dina hal ieu bakal ngaoptimalkeun jalur panas, nyaéta, kéngingkeun nilai tina kamus tanpa alokasi sareng beban anu teu perlu dina GC, sedengkeun dina kasus anu jarang urang masih kedah angkat ka IO pikeun data. , sagalana bakal tetep plus / minus cara heubeul:

public ValueTask<string> GetById(int id) {

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

Hayu urang nyandak hiji tampilan ngadeukeutan dina sapotong kode ieu: lamun aya nilai dina cache, urang nyieun struktur, disebutkeun tugas nyata bakal dibungkus dina hiji bermakna. Kodeu nélépon henteu paduli jalur mana kode ieu dieksekusi: ValueTask, tina sudut pandang sintaksis C #, bakal kalakuanana sami sareng Tugas biasa dina hal ieu.

TaskSchedulers: ngatur strategi peluncuran tugas

API salajengna anu abdi hoyong mertimbangkeun nyaéta kelas Penjadwal Tugas jeung turunanana. Kuring geus disebutkeun di luhur yén TPL mibanda kamampuhan pikeun ngatur strategi pikeun ngadistribusikaeun Tugas sakuliah threads. Strategi sapertos didefinisikeun dina turunan kelas TaskScheduler. Ampir strategi naon waé anu anjeun peryogikeun tiasa dipendakan di perpustakaan. ParallelExtensionsExtras, dikembangkeun ku Microsoft, tapi teu bagian tina .NET, tapi disadiakeun salaku pakét Nuget. Hayu urang sakeudeung ningali sababaraha di antarana:

  • CurrentThreadTaskScheduler - executes Tugas dina thread ayeuna
  • LimitedConcurrencyLevelTaskScheduler - ngawatesan jumlah Tugas anu dieksekusi sakaligus ku parameter N, anu katampi dina konstruktor.
  • OrderedTaskScheduler — diartikeun LimitedConcurrencyLevelTaskScheduler (1), jadi tugas bakal dieksekusi sequentially.
  • WorkStealingTaskScheduler - ngalaksanakeun karya-maling pendekatan kana distribusi tugas. Intina mangrupikeun ThreadPool anu misah. Ngarengsekeun masalah anu di .NET ThreadPool mangrupakeun kelas statik, hiji pikeun sakabéh aplikasi, nu hartina overloading na atawa pamakéan lepat dina hiji bagian tina program bisa ngakibatkeun efek samping di sejen. Leuwih ti éta, hésé pisan ngartos anu ngabalukarkeun defects misalna. Anu. Panginten peryogi nganggo WorkStealingTaskSchedulers anu misah dina bagian tina program dimana panggunaan ThreadPool tiasa agrésif sareng teu tiasa diprediksi.
  • QeuedTaskScheduler - ngidinan Anjeun pikeun ngalakukeun tugas nurutkeun aturan antrian prioritas
  • ThreadPerTaskScheduler - nyiptakeun utas anu misah pikeun tiap Tugas anu dilaksanakeun di dinya. Bisa jadi mangpaat pikeun tugas nu butuh waktu unpredictably lila pikeun réngsé.

Aya rinci alus artikel ngeunaan TaskSchedulers dina blog microsoft.

Pikeun debugging merenah tina sagalana patali Tugas, Visual Studio boga jandela Tugas. Dina jandela ieu anjeun tiasa ningali kaayaan tugas ayeuna sareng luncat kana garis kode anu ayeuna dieksekusi.

.NET: Alat pikeun gawé bareng multithreading na Asynchrony. Bagian 1

PLinq sareng kelas Paralel

Salian Tugas jeung sagalana ceuk ngeunaan eta, aya dua parabot leuwih metot di .NET: PLinq (Linq2Parallel) jeung kelas Parallel. Anu munggaran ngajangjikeun palaksanaan paralel sadaya operasi Linq dina sababaraha utas. Jumlah benang tiasa dikonpigurasi nganggo metode extension WithDegreeOfParallelism. Hanjakalna, paling sering PLinq dina modeu standarna henteu gaduh inpormasi anu cukup ngeunaan internal sumber data anjeun pikeun nyayogikeun kagancangan anu signifikan, di sisi anu sanés, biaya nyobian rendah pisan: anjeun ngan ukur kedah nyauran metode AsParallel sateuacana. ranté tina métode Linq tur ngajalankeun tés kinerja. Leuwih ti éta, kasebut nyaéta dimungkinkeun pikeun ngalirkeun informasi tambahan ka PLinq ngeunaan alam sumber data anjeun ngagunakeun mékanisme Partitions. Anjeun tiasa maca deui di dieu и di dieu.

Kelas statik Paralel nyadiakeun métode pikeun iterating ngaliwatan kumpulan Foreach dina paralel, executing a Pikeun loop, sarta executing sababaraha delegasi dina paralel Invoke. Palaksanaan thread ayeuna bakal dieureunkeun nepi ka itungan réngsé. Jumlah threads bisa ngonpigurasi ku ngalirkeun ParallelOptions salaku argumen panungtungan. Anjeun oge bisa nangtukeun TaskScheduler na CancellationToken ngagunakeun pilihan.

papanggihan

Nalika kuring mimiti nyerat tulisan ieu dumasar kana bahan laporan kuring sareng inpormasi anu kuring kumpulkeun nalika kuring damel saatosna, kuring henteu nyangka bakal seueur pisan. Ayeuna, nalika pangropéa téksu dimana kuring ngetik tulisan ieu nyarioskeun ka kuring yén halaman 15 parantos angkat, kuring bakal nyimpulkeun hasil interim. Trik sejen, API, alat visual jeung pitfalls bakal katutupan dina artikel salajengna.

conclusions:

  • Anjeun kedah terang alat-alat pikeun damel sareng benang, asinkron sareng paralelisme pikeun ngagunakeun sumber daya PC modern.
  • .NET boga loba parabot béda pikeun tujuan ieu
  • Henteu sadayana muncul sakaligus, janten anjeun sering tiasa mendakan warisan, tapi, aya cara pikeun ngarobih API lami tanpa seueur usaha.
  • Gawe sareng threads di .NET digambarkeun ku Thread na ThreadPool kelas
  • Metodeu Thread.Abort, Thread.Interrupt, sareng Win32 API TerminateThread bahaya sareng henteu disarankeun pikeun dianggo. Gantina, eta leuwih hade migunakeun mékanisme CancellationToken
  • Aliran mangrupikeun sumber anu berharga sareng pasokanna terbatas. Situasi dimana benang sibuk ngantosan acara kedah dihindari. Pikeun ieu merenah ngagunakeun kelas TaskCompletionSource
  • Alat .NET anu paling kuat sareng canggih pikeun damel sareng paralelisme sareng asinkron nyaéta Tugas.
  • Operator c # async / await ngalaksanakeun konsép ngantosan non-blocking
  • Anjeun tiasa ngontrol panyebaran Tugas dina utas nganggo kelas turunan TaskScheduler
  • Struktur ValueTask tiasa mangpaat dina ngaoptimalkeun hot-jalur sareng lalu lintas memori
  • Visual Studio's Tasks and Threads windows nyadiakeun seueur inpormasi anu mangpaat pikeun debugging kode multi-threaded atanapi Asynchronous
  • PLinq mangrupikeun alat anu saé, tapi panginten henteu gaduh inpormasi anu cekap ngeunaan sumber data anjeun, tapi ieu tiasa dibenerkeun nganggo mékanisme partisi.
  • Ngalajengkeun…

sumber: www.habr.com

Tambahkeun komentar