.NET: Piranti kanggo nggarap multithreading lan asynchrony. Bagean 1

Aku nerbitake artikel asli ing Habr, terjemahan sing dikirim ing perusahaan blog.

Keperluan kanggo nindakake soko asynchronously, tanpa ngenteni asil kene lan saiki, utawa kanggo dibagi karya gedhe antarane sawetara Unit Performing iku, ana sadurunge tekane saka komputer. Kanthi tekane, kabutuhan iki dadi nyata banget. Saiki, ing 2019, aku ngetik artikel iki ing laptop kanthi prosesor Intel Core 8-inti, sing luwih saka satus proses mlaku bebarengan, lan luwih akeh benang. Nearby, ana telpon rada lusuh, tuku sawetara taun kepungkur, wis prosesor 8-inti ing Papan. Sumber daya tematik kebak artikel lan video ing ngendi penulise ngujo smartphone unggulan taun iki sing nduweni prosesor 16-inti. MS Azure nyedhiyakake mesin virtual kanthi prosesor inti 20 lan 128 TB RAM kurang saka $ 2 / jam. Sayange, ora bisa ngekstrak maksimal lan nggunakake kekuwatan iki tanpa bisa ngatur interaksi benang.

Istilah kasebut

Proses - Obyek OS, papan alamat sing diisolasi, ngemot benang.
Utas - obyek OS, unit eksekusi paling cilik, bagean saka proses, benang nuduhake memori lan sumber daya liyane ing antarane proses.
Multitasking - Properti OS, kemampuan kanggo mbukak sawetara proses bebarengan
Multi-inti - properti saka prosesor, kemampuan kanggo nggunakake sawetara inti kanggo Processing data
Multiprocessing - properti saka komputer, kemampuan kanggo bebarengan karo sawetara prosesor fisik
Multithreading - properti saka proses, kemampuan kanggo nyebarake pangolahan data ing antarane sawetara utas.
Paralelisme - nindakake sawetara tumindak sacara fisik bebarengan saben unit wektu
Asynchrony - eksekusi operasi tanpa ngenteni rampung proses iki; asil eksekusi bisa diproses mengko.

Meta

Ora kabeh definisi apik lan sawetara mbutuhake panjelasan tambahan, mula aku bakal nambah metafora babagan masak sarapan menyang terminologi sing dikenalake kanthi resmi. Masak sarapan ing metafora iki minangka proses.

Nalika nyiyapake sarapan esuk aku (CPU)aku menyang pawon (Komputer). Aku duwe 2 tangan (intine). Ana sawetara piranti ing pawon (IO): oven, ceret, toaster, kulkas. Aku nguripake gas, sijine wajan ing wajan lan pour lenga menyang tanpa ngenteni panas (asynchronously, Non-Blocking-IO-Ngenteni), Aku njupuk endhog saka kulkas lan break menyang piring, banjur ngalahake karo tangan siji (Utas #1), lan kapindho (Utas #2) nyekel piring (Resource Shared). Saiki aku pengin nguripake ketel, nanging aku ora duwe cukup tangan (Thread Keluwen) Sajrone wektu iki, wajan dadi panas (Ngolah asil) sing diwutahake apa sing wis dikocok. Aku njupuk ceret lan nguripake lan bodho nonton banyu godhok ing (Bloking-IO-Ngenteni), senajan sak iki dheweke bisa ngumbah piring sing dikocok dadar.

Aku masak omelette mung nggunakake 2 tangan, lan aku ora duwe liyane, nanging ing wektu sing padha, ing wayahe mecut omelet, 3 operasi njupuk Panggonan bebarengan: whipping omelet, nyekeli piring, panas wajan. CPU minangka bagéan paling cepet saka komputer, IO iku paling asring kabeh slows mudhun, supaya asring solusi efektif kanggo Occupy CPU karo soko nalika nampa data saka IO.

Terusake metafora:

  • Yen ing proses nyiyapake omelet, aku uga bakal nyoba ngganti sandhangan, iki bakal dadi conto multitasking. Nuansa penting: komputer luwih apik tinimbang wong.
  • Pawon karo sawetara koki, contone ing restoran - komputer multi-inti.
  • Akeh restoran ing food court ing pusat blanja - pusat data

.NET Tools

.NET apik ing nggarap Utas, kaya karo akeh liyane. Kanthi saben versi anyar, ngenalake luwih akeh alat anyar kanggo nggarap, lapisan anyar abstraksi liwat benang OS. Nalika nggarap pambangunan abstraksi, pangembang kerangka nggunakake pendekatan sing ninggalake kesempatan, nalika nggunakake abstraksi tingkat dhuwur, kanggo mudhun siji utawa luwih tingkat ing ngisor iki. Paling asring iki ora perlu, nyatane mbukak lawang kanggo njupuk dhewe ing sikil nganggo bedhil, nanging kadhangkala, ing kasus-kasus langka, bisa dadi siji-sijine cara kanggo ngatasi masalah sing ora ditanggulangi ing tingkat abstraksi saiki. .

Miturut alat, maksudku loro antarmuka program aplikasi (API) sing diwenehake dening framework lan paket pihak katelu, uga solusi piranti lunak kabeh sing nyederhanakake panelusuran kanggo masalah apa wae sing ana gandhengane karo kode multi-threaded.

Miwiti thread

Kelas Utas minangka kelas paling dhasar ing .NET kanggo nggarap benang. Konstruktor nampa salah siji saka rong delegasi:

  • ThreadStart - Ora ana paramèter
  • ParametrizedThreadStart - kanthi siji parameter obyek jinis.

Delegasi bakal dieksekusi ing thread sing mentas digawe sawise nelpon metode Start.Yen utusan saka jinis ParametrizedThreadStart diterusake menyang konstruktor, banjur obyek kudu diterusake menyang metode Start. Mekanisme iki dibutuhake kanggo nransfer informasi lokal menyang stream. Wigati dicathet yen nggawe benang minangka operasi sing larang, lan benang kasebut minangka barang sing abot, paling ora amarga menehi memori 1MB ing tumpukan lan mbutuhake interaksi karo API OS.

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

Kelas ThreadPool nggambarake konsep blumbang. Ing .NET, blumbang thread minangka bagean saka teknik, lan para pangembang ing Microsoft wis ngupayakake supaya bisa digunakake kanthi optimal ing macem-macem skenario.

Konsep umum:

Wiwit aplikasi diwiwiti, nggawe sawetara utas ing latar mburi lan menehi kemampuan kanggo nggunakake. Yen benang kerep digunakake lan akeh, blumbang bakal ngembang kanggo nyukupi kabutuhan sing nelpon. Nalika ora ana utas gratis ing blumbang ing wektu sing tepat, bakal ngenteni salah sawijining utas bali, utawa nggawe sing anyar. Iku nderek sing blumbang thread apik kanggo sawetara tumindak short-term lan kurang cocog kanggo operasi sing mbukak minangka layanan saindhenging kabeh operasi saka aplikasi.

Kanggo nggunakake utas saka blumbang, ana cara QueueUserWorkItem sing nampa utusan saka jinis WaitCallback, kang wis teken padha ParametrizedThreadStart, lan parameter liwati kanggo nindakake fungsi sing padha.

ThreadPool.QueueUserWorkItem(...);

Metode pool thread sing kurang dikenal RegisterWaitForSingleObject digunakake kanggo ngatur operasi IO non-blocking. Delegasi sing diterusake menyang metode iki bakal diarani nalika WaitHandle diterusake menyang metode kasebut "Dibebasake".

ThreadPool.RegisterWaitForSingleObject(...)

.NET wis wektu thread lan iku beda saka WinForms / WPF timer ing handler bakal disebut ing thread dijupuk saka blumbang.

System.Threading.Timer

Ana uga cara sing rada eksotis kanggo ngirim utusan kanggo eksekusi menyang thread saka blumbang - metode BeginInvoke.

DelegateInstance.BeginInvoke

Aku kaya kanggo sedhela manggon ing fungsi kang akeh cara ndhuwur bisa disebut - CreateThread saka Kernel32.dll Win32 API. Ana cara, thanks kanggo mekanisme cara extern, kanggo nelpon fungsi iki. Aku wis ndeleng telpon kaya mung sapisan ing conto elek saka kode warisan, lan motivasi saka penulis sing nindakake persis iki isih tetep misteri kanggo kula.

Kernel32.dll CreateThread

Ndeleng lan Debugging Utas

Utas sing digawe sampeyan, kabeh komponen pihak katelu, lan blumbang .NET bisa dideleng ing jendhela Utas Visual Studio. Jendhela iki mung bakal nampilake informasi utas nalika aplikasi ana ing debug lan ing mode Break. Ing kene sampeyan bisa kanthi gampang ndeleng jeneng tumpukan lan prioritas saben thread, lan ngalih debugging menyang thread tartamtu. Nggunakake properti Prioritas kelas Utas, sampeyan bisa nyetel prioritas utas, sing OC lan CLR bakal dianggep minangka rekomendasi nalika mbagi wektu prosesor antarane benang.

.NET: Piranti kanggo nggarap multithreading lan asynchrony. Bagean 1

Pustaka Paralel Tugas

Task Parallel Library (TPL) dikenalaké ing .NET 4.0. Saiki iku standar lan alat utama kanggo nggarap asinkron. Sembarang kode sing nggunakake pendekatan lawas dianggep warisan. Unit dhasar TPL yaiku kelas Tugas saka ruang jeneng System.Threading.Tasks. Tugas minangka abstraksi liwat utas. Kanthi versi anyar saka basa C #, kita entuk cara sing elegan kanggo nggarap Tugas - operator async/await. Konsep-konsep iki bisa kanggo nulis kode bedo kaya prasaja lan sinkron, iki ndadekake iku bisa malah kanggo wong karo sethitik pangerten saka internal workings Utas nulis aplikasi sing digunakake, aplikasi sing ora beku nalika nindakake operasi dawa. Nggunakake async/await minangka topik kanggo siji utawa malah sawetara artikel, nanging aku bakal nyoba ngerteni inti ing sawetara ukara:

  • async minangka modifier saka cara ngasilake Tugas utawa ora sah
  • lan ngenteni minangka operator nunggu Tugas sing ora ngalangi.

Sawise maneh: operator ngenteni, ing kasus umum (ana pangecualian), bakal ngeculake utas eksekusi saiki luwih lanjut, lan nalika Tugas rampung eksekusi, lan utas kasebut (nyatane, bakal luwih bener ngomong konteks kasebut. , nanging luwih akeh babagan mengko) bakal terus nglakokake metode kasebut. Nang .NET, mekanisme iki dipun ginakaken ing cara sing padha ngasilaken bali, nalika cara ditulis dadi kabèh kelas, kang mesin negara lan bisa kaleksanan ing bêsik kapisah gumantung ing negara iki. Sapa wae sing kasengsem bisa nulis kode prasaja nggunakake asynс/await, ngumpulake lan ndeleng perakitan nggunakake JetBrains dotPeek kanthi Kode Generasi Compiler diaktifake.

Ayo goleki opsi kanggo mbukak lan nggunakake Tugas. Ing conto kode ing ngisor iki, kita nggawe tugas anyar sing ora ana gunane (Thread.Sleep(10000)), nanging ing urip nyata iki kudu sawetara karya CPU-intensif Komplek.

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 digawe kanthi sawetara opsi:

  • LongRunning minangka tandha yen tugas kasebut ora bakal rampung kanthi cepet, tegese bisa uga kudu dipikirake ora njupuk benang saka blumbang, nanging nggawe sing kapisah kanggo Tugas iki supaya ora cilaka wong liya.
  • AttachedToParent - Tugas bisa diatur ing hirarki. Yen pilihan iki digunakake, banjur Tugas bisa uga ana ing negara sing wis rampung lan ngenteni eksekusi anak-anake.
  • PreferFairness - tegese luwih becik nglakokake Tugas sing dikirim kanggo eksekusi sadurunge sadurunge dikirim mengko. Nanging iki mung rekomendasi lan asil ora dijamin.

Parameter kapindho sing diterusake menyang metode kasebut yaiku CancellationToken. Kanggo nangani pembatalan operasi kanthi bener sawise diwiwiti, kode sing dieksekusi kudu diisi cek kanggo negara CancellationToken. Yen ora ana mriksa, banjur metode Batal sing diarani obyek CancellationTokenSource bakal bisa mungkasi eksekusi Tugas mung sadurunge diwiwiti.

Parameter pungkasan yaiku obyek penjadwal saka jinis TaskScheduler. Kelas iki lan turunane dirancang kanggo ngontrol strategi kanggo nyebarake Tugas ing benang; kanthi gawan, Tugas kasebut bakal dieksekusi ing benang acak saka blumbang.

Operator ngenteni ditrapake kanggo Tugas sing digawe, tegese kode sing ditulis sawise, yen ana, bakal dieksekusi ing konteks sing padha (asring tegese ing benang sing padha) karo kode sadurunge ngenteni.

Cara kasebut ditandhani minangka async void, tegese bisa nggunakake operator await, nanging kode panggilan ora bisa ngenteni eksekusi. Yen fitur kasebut perlu, mula cara kasebut kudu ngasilake Tugas. Cara sing ditandhani async void cukup umum: minangka aturan, iki minangka panangan acara utawa cara liya sing bisa digunakake ing prinsip geni lan lali. Yen sampeyan ora mung kudu menehi kesempatan kanggo ngenteni nganti pungkasan eksekusi, nanging uga ngasilake asil, mula sampeyan kudu nggunakake Tugas.

Ing Tugas sing cara StartNew bali, uga ing liyane, sampeyan bisa nelpon cara ConfigureAwait karo parameter palsu, banjur eksekusi sawise ngenteni bakal terus ora ing konteks dijupuk, nanging ing sembarang sewenang-wenang. Iki kudu ditindakake nalika konteks eksekusi ora penting kanggo kode sawise ngenteni. Iki uga minangka rekomendasi saka MS nalika nulis kode sing bakal dikirim ing perpustakaan.

Ayo dipikirake luwih akeh babagan carane sampeyan bisa ngenteni rampunge Tugas. Ing ngisor iki conto kode, kanthi komentar nalika pangarep-arep ditindakake kanthi apik lan nalika ditindakake kanthi apik.

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
}

Ing conto pisanan, kita ngenteni Tugas rampung tanpa ngalangi thread nelpon; kita bakal bali kanggo ngolah asil mung yen wis ana; nganti saiki, thread nelpon ditinggalake menyang piranti dhewe.

Ing pilihan kapindho, kita mblokir thread nelpon nganti asil saka cara wis diwilang. Iki ala ora mung amarga kita wis dikuwasani thread, kuwi sumber terkenal saka program, karo idleness prasaja, nanging uga amarga yen kode saka cara sing kita nelpon ngemot ngenteni, lan konteks sinkronisasi mbutuhake bali menyang thread nelpon sawise. ngenteni, banjur kita bakal entuk deadlock: Utas nelpon ngenteni asil saka metode asinkron sing bakal diwilang, cara asinkron nyoba muspra kanggo nerusake eksekusi ing thread panggilan.

Kerugian liyane saka pendekatan iki yaiku penanganan kesalahan sing rumit. Kasunyatane, kesalahan ing kode asinkron nalika nggunakake async / ngenteni gampang banget ditangani - padha tumindak kaya kode kasebut sinkron. Nalika kita ngetrapake eksorsisme ngenteni sinkron menyang Tugas, pengecualian asli dadi AggregateException, yaiku. Kanggo nangani istiméwa, sampeyan kudu nliti jinis InnerException lan nulis yen chain dhewe nang siji pemblokiran nyekel utawa nggunakake nyekel nalika mbangun, tinimbang chain pamblokiran nyekel sing luwih menowo ing C # donya.

Conto katelu lan pungkasan uga ditandhani ala kanggo alasan sing padha lan ngemot kabeh masalah sing padha.

Cara WhenAny lan WhenAll trep banget kanggo ngenteni klompok Tugas; padha mbungkus klompok Tugas dadi siji, sing bakal murub nalika Tugas saka grup kasebut pisanan dipicu, utawa nalika kabeh wis rampung eksekusi.

Mungkasi benang

Kanggo macem-macem alasan, bisa uga kudu mandhegake aliran sawise diwiwiti. Ana sawetara cara kanggo nindakake iki. Kelas Thread duwe rong cara sing cocog: Aborsi и Ngganggu. Sing pertama banget ora dianjurake kanggo digunakake, amarga sawise nelpon ing sembarang wayahe acak, sak Processing instruksi sembarang, pangecualian bakal di buwang ThreadAbortedException. Sampeyan ora ngarepake pangecualian kasebut bakal dibuwang nalika nambah variabel integer, ta? Lan nalika nggunakake metode iki, iki kahanan sing nyata banget. Yen sampeyan kudu nyegah CLR ngasilake pangecualian kasebut ing bagean kode tartamtu, sampeyan bisa mbungkus ing telpon. Thread.BeginCriticalRegion, Thread.EndCriticalRegion. Sembarang kode sing ditulis ing blok pungkasan dibungkus ing telpon kasebut. Mulane, ing ambane kode framework sampeyan bisa nemokake pamblokiran karo nyoba kosong, nanging ora kosong pungkasanipun. Microsoft nyurung cara iki supaya ora kalebu ing inti .net.

Cara Interrupt kerjane luwih bisa ditebak. Bisa ngganggu utas kanthi pangecualian ThreadInterruptedException mung ing wektu-wektu kasebut nalika thread lagi nunggu. Iku lumebu ing negara iki nalika hanging nalika nunggu WaitHandle, kunci, utawa sawise nelpon Thread.Sleep.

Loro-lorone opsi sing diterangake ing ndhuwur iku ala amarga ora bisa diprediksi. Solusi yaiku nggunakake struktur PembatalanToken lan kelas CancellationTokenSource. Intine yaiku: conto kelas CancellationTokenSource digawe lan mung sing duwe bisa mungkasi operasi kasebut kanthi nelpon metode kasebut. Batal. Mung CancellationToken sing diterusake menyang operasi kasebut. Pamilik CancellationToken ora bisa mbatalake operasi kasebut dhewe, nanging mung bisa mriksa manawa operasi kasebut dibatalake. Ana properti Boolean kanggo iki IsCancellationRequested lan metode ThrowIfCancelRequested. Sing terakhir bakal mbuwang pengecualian TaskCancelledException yen metode Batal diarani ing Kayata CancellationToken kang parroted. Lan iki cara aku nyaranake nggunakake. Iki minangka asil dandan saka opsi sadurunge kanthi entuk kontrol lengkap babagan ing ngendi operasi pangecualian bisa dibatalake.

Pilihan sing paling brutal kanggo mungkasi thread yaiku nelpon fungsi Win32 API TerminateThread. Prilaku CLR sawise nelpon fungsi iki bisa uga ora bisa ditebak. Ing MSDN ing ngisor iki ditulis babagan fungsi iki: "TerminateThread minangka fungsi mbebayani sing mung kudu digunakake ing kasus sing paling ekstrim. “

Ngonversi API warisan dadi Task Based nggunakake metode FromAsync

Yen sampeyan cukup beruntung bisa nggarap proyek sing diwiwiti sawise Tugas dienalake lan mandheg nyebabake medeni sepi kanggo umume pangembang, mula sampeyan ora kudu ngatasi akeh API lawas, loro pihak katelu lan tim sampeyan. wis tortured ing sasi. Untunge, tim .NET Framework ngurus kita, sanajan bisa uga tujuane kanggo ngurus awake dhewe. Apa wae, .NET duwe sawetara alat kanggo ngonversi kode tanpa rasa sakit sing ditulis ing pendekatan program bedo lawas menyang sing anyar. Salah sijine yaiku metode FromAsync saka TaskFactory. Ing conto kode ing ngisor iki, aku Lebokake metode bedo lawas saka kelas WebRequest ing Tugas nggunakake cara iki.

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

Iki mung minangka conto lan sampeyan ora kudu nindakake iki karo jinis sing wis dibangun, nanging proyek lawas mung akeh karo metode BeginDoSomething sing ngasilake metode IAsyncResult lan EndDoSomething sing nampa.

Ngonversi API warisan menyang Task Based nggunakake kelas TaskCompletionSource

Alat penting liyane sing kudu ditimbang yaiku kelas Sumber Tugas. Ing babagan fungsi, tujuan lan prinsip operasi, bisa uga kaya cara RegisterWaitForSingleObject saka kelas ThreadPool, sing aku tulis ing ndhuwur. Nggunakake kelas iki, sampeyan bisa kanthi gampang lan gampang mbungkus API asynchronous lawas ing Tugas.

Sampeyan bakal ujar manawa aku wis ngomong babagan metode FromAsync saka kelas TaskFactory sing dimaksudake kanggo tujuan kasebut. Ing kene kita kudu ngelingi kabeh sejarah pangembangan model asinkron ing .net sing ditawakake Microsoft sajrone 15 taun kepungkur: sadurunge Pola Asynchronous Berbasis Tugas (TAP), ana Pola Pemrograman Asynchronous (APP), sing ana babagan metode miwitiDoSomething bali IAsyncResult lan cara AkhirDoSomething sing nampa lan kanggo warisan taun-taun iki, metode FromAsync mung sampurna, nanging suwe-suwe, diganti karo Pola Asynchronous Based Event (LAN AP), sing nganggep yen acara bakal diunggahake nalika operasi asinkron rampung.

TaskCompletionSource sampurna kanggo mbungkus Tugas lan API warisan sing dibangun ing model acara. Inti saka karyane kaya ing ngisor iki: obyek saka kelas iki nduweni properti umum saka jinis Tugas, negara sing bisa dikontrol liwat metode SetResult, SetException, lan liya-liyane saka kelas TaskCompletionSource. Ing panggonan sing operator ngenteni ditrapake kanggo Tugas iki, bakal dieksekusi utawa gagal kanthi pangecualian gumantung saka metode sing ditrapake ing TaskCompletionSource. Yen isih durung jelas, ayo goleki conto kode iki, ing ngendi sawetara API EAP lawas dibungkus Tugas nggunakake TaskCompletionSource: nalika acara kasebut murub, Tugas bakal ditransfer menyang negara Rampung, lan metode sing ditrapake operator ngenteni. kanggo Tugas iki bakal nerusake eksekusi sawise nampa obyek kasebut asil.

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

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

    result completionSource.Task;
}

Tips & Trik Sumber Tugas

Bungkus API lawas ora kabeh sing bisa ditindakake nggunakake TaskCompletionSource. Nggunakake kelas iki mbukak kamungkinan menarik kanggo ngrancang macem-macem API ing Tugas sing ora Occupy Utas. Lan stream, kaya sing kita eling, minangka sumber daya sing larang lan jumlahe diwatesi (utamane kanthi jumlah RAM). Watesan iki bisa gampang digayuh kanthi ngembangake, contone, aplikasi web sing dimuat kanthi logika bisnis sing rumit. Ayo dipikirake kemungkinan sing dakkandhakake nalika ngetrapake trik kaya Long-Polling.

Ing cendhak, intine trick iki: sampeyan kudu nampa informasi saka API babagan sawetara acara sing kedadeyan ing sisihe, nalika API, sakperangan alesan, ora bisa nglaporake acara kasebut, nanging mung bisa ngasilake negara. Conto iki kabeh API dibangun ing ndhuwur HTTP sadurunge kaping WebSocket utawa nalika iku mokal kanggo sawetara alesan kanggo nggunakake teknologi iki. Klien bisa takon server HTTP. Server HTTP ora bisa miwiti komunikasi karo klien. Solusi sing gampang yaiku polling server nggunakake timer, nanging iki nggawe beban tambahan ing server lan wektu tundha tambahan rata-rata TimerInterval / 2. Kanggo ngubengi iki, trik sing diarani Long Polling diciptakake, sing kalebu tundha tanggapan saka server nganti wektu entek kadaluwarsa utawa acara bakal kelakon. Yen acara wis kedadeyan, banjur diproses, yen ora, panyuwunan dikirim maneh.

while(!eventOccures && !timeoutExceeded)  {

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

Nanging solusi kuwi bakal mbuktekaken dadi elek sanalika jumlah klien nunggu acara mundhak, amarga ... Saben klien kuwi manggoni kabeh thread nunggu acara. Ya, lan kita entuk wektu tundha 1ms tambahan nalika acara kasebut dipicu, paling asring iki ora penting, nanging kenapa piranti lunak luwih elek tinimbang bisa? Yen mbusak Thread.Sleep(1), muspra kita bakal mbukak siji inti prosesor 100% nganggur, muter ing siklus ora ana guna. Nggunakake TaskCompletionSource sampeyan bisa nggawe maneh kode iki kanthi gampang lan ngrampungake kabeh masalah sing diidentifikasi ing ndhuwur:

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 iki ora siap produksi, nanging mung demo. Kanggo nggunakake ing kasus nyata, sampeyan uga kudu, paling sethithik, kanggo nangani kahanan nalika pesen teka ing wektu sing ora ana sing dikarepake: ing kasus iki, cara AsseptMessageAsync kudu ngasilake Tugas sing wis rampung. Yen iki kasus sing paling umum, sampeyan bisa mikir babagan nggunakake ValueTask.

Nalika kita nampa panjalukan kanggo pesen, kita nggawe lan nyeleh TaskCompletionSource ing kamus, banjur ngenteni apa pisanan: interval wektu sing ditemtokake kadaluwarsa utawa pesen ditampa.

ValueTask: kenapa lan kepiye

Operator async / ngenteni, kaya operator ngasilake, ngasilake mesin negara saka metode kasebut, lan iki nggawe obyek anyar, sing meh ora penting, nanging ing kasus sing jarang, bisa nggawe masalah. Kasus iki bisa dadi cara sing asring diarani, kita ngomong babagan puluhan lan atusan ewu telpon per detik. Yen cara kasebut ditulis kanthi cara sing umume ngasilake asil ngliwati kabeh metode ngenteni, banjur .NET nyedhiyakake alat kanggo ngoptimalake iki - struktur ValueTask. Kanggo nggawe cetha, ayo goleki conto panggunaane: ana cache sing asring banget. Ana sawetara nilai ing kono banjur kita bali maneh; yen ora, banjur pindhah menyang sawetara IO alon kanggo njaluk. Aku pengin nindakake sing terakhir kanthi asynchronous, tegese kabeh cara dadi ora sinkron. Dadi, cara sing jelas kanggo nulis metode kasebut yaiku:

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

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

Amarga kepinginan kanggo ngoptimalake sethithik, lan rada wedi karo apa sing bakal ditindakake Roslyn nalika nyusun kode iki, sampeyan bisa nulis ulang conto iki kaya ing ngisor iki:

public Task<string> GetById(int id) {

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

Pancen, solusi sing paling optimal ing kasus iki yaiku ngoptimalake jalur panas, yaiku, entuk nilai saka kamus tanpa alokasi lan mbukak GC sing ora perlu, dene ing kasus-kasus langka nalika isih kudu pindhah menyang IO kanggo data. , kabeh bakal tetep plus / minus cara lawas:

public ValueTask<string> GetById(int id) {

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

Ayo kita nliti potongan kode iki: yen ana nilai ing cache, kita nggawe struktur, yen ora, tugas nyata bakal dibungkus kanthi migunani. Kode panggilan ora preduli ing dalan sing dieksekusi kode iki: ValueTask, saka sudut pandang sintaksis C #, bakal tumindak padha karo Tugas biasa ing kasus iki.

TaskSchedulers: ngatur strategi peluncuran tugas

API sabanjure sing dakkarepake yaiku kelas Penjadwal Tugas lan turunane. Aku wis kasebut ing ndhuwur yen TPL nduweni kemampuan kanggo ngatur strategi kanggo nyebarake Tugas ing benang. Sastranegara kasebut ditetepake ing turunan saka kelas TaskScheduler. Meh kabeh strategi sing sampeyan butuhake bisa ditemokake ing perpustakaan. ParallelExtensionsExtras, dikembangaké dening Microsoft, nanging ora bagean .NET, nanging diwenehake minangka paket Nuget. Ayo katon sedhela sawetara saka wong-wong mau:

  • CurrentThreadTaskScheduler - nglakokake Tugas ing utas saiki
  • LimitedConcurrencyLevelTaskScheduler - matesi jumlah Tugas sing dieksekusi bebarengan dening parameter N, sing ditampa ing konstruktor
  • OrderedTaskScheduler — ditetepake minangka LimitedConcurrencyLevelTaskScheduler (1), supaya tugas bakal dieksekusi kanthi urutan.
  • WorkStealingTaskScheduler - ngleksanakake nyolong karya pendekatan kanggo distribusi tugas. Intine iku ThreadPool sing kapisah. Ngatasi masalah sing ing .NET ThreadPool kelas statis, siji kanggo kabeh aplikasi, kang tegese overloading utawa salah nggunakake ing salah siji bagéan saka program bisa mimpin kanggo efek sisih liyane. Kajaba iku, angel banget kanggo ngerti sababe cacat kasebut. Iku. Bisa uga ana kabutuhan nggunakake WorkStealingTaskSchedulers sing kapisah ing bagean program sing nggunakake ThreadPool bisa uga agresif lan ora bisa ditebak.
  • QueuedTaskScheduler - ngidini sampeyan nindakake tugas miturut aturan antrian prioritas
  • ThreadPerTaskScheduler - nggawe thread kapisah kanggo saben Tugas sing dileksanakake ing. Bisa migunani kanggo tugas sing mbutuhake wektu sing ora bisa ditebak.

Ana rinci apik artikel babagan TaskSchedulers ing blog microsoft.

Kanggo debugging sing trep kanggo kabeh sing ana gandhengane karo Tugas, Visual Studio duwe jendela Tugas. Ing jendhela iki, sampeyan bisa ndeleng status tugas saiki lan mlumpat menyang baris kode sing lagi dieksekusi.

.NET: Piranti kanggo nggarap multithreading lan asynchrony. Bagean 1

PLinq lan kelas Paralel

Saliyane Tugas lan kabeh ngandika bab wong-wong mau, ana loro alat liyane menarik ing .NET: PLinq (Linq2Parallel) lan kelas Podo. Pisanan njanjeni eksekusi paralel kabeh operasi Linq ing pirang-pirang utas. Jumlah utas bisa dikonfigurasi nggunakake metode ekstensi WithDegreeOfParallelism. Sayange, paling asring PLinq ing mode standar ora duwe informasi sing cukup babagan internal sumber data kanggo nyedhiyakake gain kacepetan sing signifikan, ing sisih liya, biaya nyoba sithik banget: sampeyan mung kudu nelpon metode AsParallel sadurunge chain cara Linq lan mbukak tes kinerja. Kajaba iku, sampeyan bisa ngirim informasi tambahan menyang PLinq babagan sifat sumber data sampeyan nggunakake mekanisme Partisi. Sampeyan bisa maca liyane kene и kene.

Kelas statis Paralel nyedhiyakake cara kanggo ngulang liwat koleksi Foreach kanthi paralel, nglakokake loop For, lan nglakokake sawetara utusan ing Invoke paralel. Eksekusi utas saiki bakal mandheg nganti petungan rampung. Jumlah utas bisa dikonfigurasi kanthi ngliwati ParallelOptions minangka argumen pungkasan. Sampeyan uga bisa nemtokake TaskScheduler lan CancellationToken nggunakake opsi.

temonan

Nalika aku miwiti nulis artikel iki adhedhasar materi laporan lan informasi sing aku diklumpukake sak karya sawise iku, Aku ora nyana bakal ana akeh banget. Saiki, nalika editor teks sing aku ngetik artikel iki kanthi reproachly ngandhani yen kaca 15 wis ilang, aku bakal ngringkes asil interim. Trik liyane, API, alat visual lan pitfalls bakal dibahas ing artikel sabanjure.

Kesimpulan:

  • Sampeyan kudu ngerti alat kanggo nggarap benang, asinkron lan paralelisme supaya bisa nggunakake sumber daya PC modern.
  • .NET duwe macem-macem alat kanggo tujuan kasebut
  • Ora kabeh muncul bebarengan, mula sampeyan bisa nemokake warisan, nanging ana cara kanggo ngowahi API lawas tanpa gaweyan.
  • Nggarap thread ing .NET diwakili dening kelas Thread lan ThreadPool
  • Metode Thread.Abort, Thread.Interrupt, lan Win32 API TerminateThread mbebayani lan ora dianjurake kanggo digunakake. Nanging, luwih becik nggunakake mekanisme CancellationToken
  • Aliran minangka sumber daya sing larang regane lan pasokane diwatesi. Kahanan ing ngendi benang sibuk ngenteni acara kudu dihindari. Iki trep kanggo nggunakake kelas TaskCompletionSource
  • Piranti .NET sing paling kuat lan canggih kanggo nggarap paralelisme lan asinkron yaiku Tugas.
  • Operator c# async/await ngleksanakake konsep tunggu non-blocking
  • Sampeyan bisa ngontrol distribusi Tugas ing benang nggunakake kelas sing diturunake TaskScheduler
  • Struktur ValueTask bisa migunani kanggo ngoptimalake jalur panas lan lalu lintas memori
  • Visual Studio's Tasks and Threads windows nyedhiyakake akeh informasi sing migunani kanggo debugging kode multi-thread utawa asinkron
  • PLinq minangka alat sing keren, nanging bisa uga ora duwe informasi sing cukup babagan sumber data sampeyan, nanging iki bisa diatasi kanthi nggunakake mekanisme pemisahan.
  • Terus ...

Source: www.habr.com

Add a comment