.NET: Асбобҳо барои кор бо чанд ришта ва асинхронӣ. Қисми 1

Ман мақолаи аслии Ҳабрро нашр мекунам, ки тарҷумаи он дар корпоративӣ ҷойгир шудааст пости блог.

Зарурати ба таври асинхронӣ анҷом додани коре, бидуни мунтазири натиҷа дар ин ҷо ва ҳоло ё тақсим кардани кори калон дар байни якчанд воҳидҳои иҷрокунандаи он, пеш аз пайдоиши компютерҳо вуҷуд дошт. Бо пайдоиши онҳо, ин талабот хеле воқеӣ гардид. Ҳоло, дар соли 2019, ман ин мақоларо дар ноутбук бо протсессори 8-аслӣ Intel Core менависам, ки дар он зиёда аз сад раванд дар мувозӣ кор мекунанд ва ҳатто риштаҳои бештар. Дар наздикӣ як телефони каме фарсуда мавҷуд аст, ки чанд сол пеш харида шуда буд, дар он протсессори 8 ядроӣ дорад. Сарчашмаҳои мавзӯӣ пур аз мақолаҳо ва видеоҳо мебошанд, ки дар он муаллифони онҳо смартфонҳои флагмани имсоларо, ки дорои протсессори 16-аслӣ мебошанд, қадр мекунанд. MS Azure як мошини маҷозӣ бо протсессори аслӣ 20 ва хотираи 128 ТБ барои камтар аз $2/соат таъмин мекунад. Мутаассифона, бе идора кардани таъсири мутақобилаи риштаҳо ҳадди аксарро ба даст овардан ва истифодаи ин нерӯ ғайриимкон аст.

Терминология

Раванд - Объекти ОС, фазои суроғаи ҷудошуда, дорои риштаҳо мебошад.
Ришта - объекти ОС, хурдтарин воҳиди иҷро, қисми раванд, риштаҳо хотира ва захираҳои дигарро байни худ дар дохили раванд мубодила мекунанд.
Multitasking - Амволи OS, қобилияти иҷро кардани якчанд равандҳо дар як вақт
Бисёр ядроӣ - хосияти протсессор, қобилияти истифодаи якчанд ядроҳо барои коркарди маълумот
Коркарди бисёрҷониба - хосияти компютер, қобилияти ҳамзамон бо якчанд протсессори ҷисмонӣ кор кардан
Мултираи — хосияти раванд, қобилияти тақсими коркарди маълумот дар байни якчанд ришта.
Параллелизм - дар як воҳиди вақт иҷро кардани якчанд амалҳои ҷисмонӣ
Асинхронӣ — ичрои амалиёт бе мунтазири ба охир расидани ин коркард, натичаи ичрои онро дертар кор кардан мумкин аст.

Методор

На ҳама таърифҳо хубанд ва баъзеҳо ба шарҳи иловагӣ ниёз доранд, аз ин рӯ ман ба истилоҳоти расман ҷорӣшуда дар бораи пухтани наҳорӣ истиора илова мекунам. Пухтани субҳона дар ин истиора як раванд аст.

Ҳангоми омода кардани субҳона ман (ВПМ - Воҳиди Пардозиши Марказӣ) Ман ба ошхона меоям (Компютер). Ман 2 даст дорам (cores). Дар ошхона як қатор дастгоҳҳо мавҷуданд (IO): танӯр, чойник, тостер, яхдон. Газро даргиронда, табақа мегузорам ва ба он равған мерезам, ки гарм шудани онро интизор нест (асинхронӣ, Non-Блоки-IO-Интизори), Тухмхоро аз яхдон бароварда ба табак мешиканам, баъд бо як даст мезанам (Мавзӯи №1), ва дуюм (Мавзӯи №2) нигоҳ доштани табақ (Захираҳои муштарак). Ҳоло мехоҳам чойникро даргирам, аммо дастам намерасад (Гуруснагии ришта) Дар ин вақт, табақи нонпазӣ гарм мешавад (коркарди натиҷа), ки ман он чизеро, ки қамчин кардаам, мерезам. Ман ба чойник даст дароз карда, онро даргирам ва аблаҳона тамошо мекунам, ки об дар он ҷӯш мешавад (Бастан-IO-Интизор шавед), гарчанде ки дар ин муддат ӯ метавонист табақеро, ки омлетро қамчин зад, бишӯяд.

Ман омлетро ҳамагӣ бо 2 даст пухтам, зиёд надорам, аммо дар айни замон дар лаҳзаи қамчин задани омлет якбора 3 амал анҷом дода шуд: қамчин задан, табақро нигоҳ доштан, табақро гарм кардан Протсессори тезтарин қисми компютер аст, IO он чизест, ки аксар вақт ҳама чиз суст мешавад, бинобар ин аксар вақт ҳалли муассир ишғол кардани CPU бо чизе ҳангоми гирифтани маълумот аз IO мебошад.

Идомаи метафора:

  • Агар дар раванди тайёр кардани омлет, ман ҳам кӯшиш мекардам, ки либосро иваз кунам, ин як мисоли бисёркорӣ хоҳад буд. Як нозуки муҳим: компютерҳо дар ин кор нисбат ба одамон хеле беҳтаранд.
  • Ошхона бо якчанд ошпазҳо, масалан, дар тарабхона - як компютери бисёрсоҳавӣ.
  • Бисёр тарабхонаҳо дар суди ғизо дар маркази савдо - маркази маълумот

Воситаҳои .NET

.NET дар кор бо риштаҳо мисли бисёр чизҳои дигар хуб аст. Бо ҳар як версияи нав, он асбобҳои бештари навро барои кор бо онҳо, қабатҳои нави абстраксия дар риштаҳои ОС ҷорӣ мекунад. Ҳангоми кор бо сохтани абстраксияҳо, таҳиягарони чаҳорчӯба равишеро истифода мебаранд, ки ҳангоми истифодаи абстраксияи сатҳи баланд имкони поин рафтани як ё якчанд сатҳҳоро тарк мекунад. Аксар вақт ин лозим нест, дар асл он дари худро ба пои худ бо туфангча мекушояд, аммо баъзан, дар ҳолатҳои нодир, он метавонад ягона роҳи ҳалли мушкилоте бошад, ки дар сатҳи кунунии абстраксия ҳал нашудааст. .

Воситаҳо, ман ҳам интерфейсҳои барномасозии барномаро (API), ки чаҳорчӯба ва бастаҳои тарафи сеюм пешниҳод мекунанд, инчунин тамоми ҳалли нармафзореро дар назар дорам, ки ҷустуҷӯи ҳама гуна мушкилоти марбут ба коди бисёрсоҳаро содда мекунанд.

Оғози ришта

Синфи Thread синфи асосӣтарин дар .NET барои кор бо риштаҳо мебошад. Конструктор яке аз ду вакилро қабул мекунад:

  • ThreadStart — Параметрҳо нест
  • ParametrizedThreadStart - бо як параметри навъи объект.

Вакил пас аз даъвати усули Start дар риштаи навтаъсис иҷро карда мешавад.Агар вакили навъи ParametrizedThreadStart ба созанда интиқол дода шуда бошад, пас объект бояд ба усули Start интиқол дода шавад. Ин механизм барои интиқоли ҳама гуна маълумоти маҳаллӣ ба ҷараён лозим аст. Бояд қайд кард, ки сохтани ришта як амалиёти гаронбаҳост ва худи ришта як объекти вазнин аст, ҳадди аққал аз он сабаб, ки он 1 МБ хотираро дар стек ҷудо мекунад ва ҳамкорӣ бо API OS-ро талаб мекунад.

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

Синфи ThreadPool консепсияи ҳавзро ифода мекунад. Дар .NET, ҳавзи ришта як ҷузъи муҳандисӣ аст ва таҳиягарон дар Microsoft кӯшиши зиёд ба харҷ додаанд, то боварӣ ҳосил кунанд, ки он дар сенарияҳои гуногун оптималӣ кор мекунад.

Консепсияи умумӣ:

Аз лаҳзаи оғози барнома, он дар замина якчанд риштаҳои захиравӣ эҷод мекунад ва қобилияти гирифтани онҳоро барои истифода фароҳам меорад. Агар риштаҳо зуд-зуд ва ба миқдори зиёд истифода шаванд, ҳавз барои қонеъ кардани ниёзҳои зангзананда васеъ мешавад. Вақте ки дар ҳавз дар вақти зарурӣ риштаҳои ройгон вуҷуд надоранд, он ё бозгашти яке аз риштаҳоро интизор мешавад ё риштаи нав эҷод мекунад. Аз ин бармеояд, ки ҳавзи ришта барои баъзе амалҳои кӯтоҳмуддат бузург аст ва барои амалиётҳое, ки дар тамоми амалиёти барнома ҳамчун хидмат кор мекунанд, чандон мувофиқ нестанд.

Барои истифодаи ришта аз ҳавз усули QueueUserWorkItem мавҷуд аст, ки вакили навъи WaitCallback-ро қабул мекунад, ки ҳамон имзои ParametrizedThreadStart дорад ва параметре, ки ба он интиқол дода шудааст, ҳамон вазифаро иҷро мекунад.

ThreadPool.QueueUserWorkItem(...);

Усули камтар маълуми ҳавзи риштаи RegisterWaitForSingleObject барои ташкили амалиёти IO-и ғайрибандӣ истифода мешавад. Вакиле, ки ба ин усул мегузарад, вақте даъват карда мешавад, ки WaitHandle ба усул интиқол дода мешавад "Озод карда шудааст".

ThreadPool.RegisterWaitForSingleObject(...)

.NET дорои таймери ришта аст ва он аз таймерҳои WinForms/WPF бо он фарқ мекунад, ки коркардкунандаи он дар риштаи аз ҳавз гирифташуда даъват карда мешавад.

System.Threading.Timer

Инчунин як роҳи хеле экзотикии фиристодани вакил барои иҷро ба ришта аз ҳавз мавҷуд аст - усули BeginInvoke.

DelegateInstance.BeginInvoke

Ман мехоҳам ба таври мухтасар дар бораи функсияе таваққуф намоям, ки бисёре аз усулҳои дар боло зикршударо метавон номид - CreateThread аз Kernel32.dll Win32 API. Ба шарофати механизми усулҳои экстернӣ роҳи даъват кардани ин функсия вуҷуд дорад. Ман танҳо як маротиба чунин зангро дар мисоли даҳшатноки рамзи мерос дида будам ва ангезаи муаллифе, ки маҳз ин корро кардааст, то ҳол барои ман як сирр боқӣ мемонад.

Kernel32.dll CreateThread

Намоиш ва ислоҳи риштаҳо

Риштаҳои эҷодкардаи шумо, ҳама ҷузъҳои тарафи сеюм ва ҳавзи .NET-ро дар равзанаи Threads Visual Studio дидан мумкин аст. Ин равзана танҳо ҳангоми ислоҳи барнома ва дар реҷаи Танаффус маълумоти ришта нишон медиҳад. Дар ин ҷо шумо метавонед ба осонӣ номҳои стек ва афзалиятҳои ҳар як риштаро бубинед ва ислоҳи ислоҳро ба риштаи мушаххас гузаронед. Бо истифода аз хосияти Priority-и синфи Thread, шумо метавонед афзалияти риштаро муқаррар кунед, ки онро OC ва CLR ҳангоми тақсими вақти протсессор байни риштаҳо ҳамчун тавсия қабул мекунанд.

.NET: Асбобҳо барои кор бо чанд ришта ва асинхронӣ. Қисми 1

Китобхонаи параллели вазифа

Китобхонаи параллелӣ (TPL) дар .NET 4.0 ҷорӣ карда шуд. Ҳоло он стандарт ва воситаи асосии кор бо асинхронӣ мебошад. Ҳама гуна рамзе, ки равиши кӯҳнаро истифода мебарад, меросӣ ҳисобида мешавад. Воҳиди асосии TPL синфи Task аз фазои номи System.Threading.Tasks мебошад. Вазифа абстраксия дар болои ришта аст. Бо версияи нави забони C#, мо як роҳи зебои кор бо Tasks - операторҳои async/await -ро гирифтем. Ин мафҳумҳо имкон доданд, ки рамзи асинхронӣ тавре навишта шаванд, ки гӯё он оддӣ ва синхронӣ бошад, ин ба одамон имкон дод, ки ҳатто дар бораи кори дохилии риштаҳо каме фаҳманд, замимаҳое, ки онҳоро истифода мебаранд, замимаҳоеро, ки ҳангоми иҷрои амалиёти тӯлонӣ ях намекунанд, нависад. Истифодаи async/await як мавзӯъ барои як ё ҳатто якчанд мақола аст, аммо ман кӯшиш мекунам, ки дар чанд ҷумла мафҳуми онро бифаҳмам:

  • async як тағирдиҳандаи усулест, ки супориш ё бекориро бармегардонад
  • ва интизорӣ як оператори интизории масдудкунанда нест.

Бори дигар: оператори интизорӣ, дар ҳолати умумӣ (истисноҳо вуҷуд доранд) риштаи ҷории иҷроро минбаъд озод мекунад ва вақте ки супориш иҷрои худро ба анҷом мерасонад, ва ришта (воқеан, контекст гуфтан дурусттар мебуд. , аммо бештар дар бораи он баъдтар) иҷрои усулро минбаъд идома медиҳад. Дар дохили .NET, ин механизм ҳамон тавре, ки ҳосили бозгашт амалӣ карда мешавад, вақте усули хаттӣ ба як синф табдил меёбад, ки мошини давлатӣ аст ва вобаста ба ин ҳолат метавонад дар қисмҳои алоҳида иҷро карда шавад. Ҳар як хоҳишманд метавонад бо истифода аз asyns/await ҳама гуна коди оддиро нависад, бо истифода аз JetBrains dotPeek бо фаъолсозии Рамзи тавлидшудаи Compiler маҷлисро тартиб диҳад ва бубинад.

Биёед имконоти оғоз ва истифодаи Вазифаро дида бароем. Дар мисоли коди дар поён овардашуда, мо вазифаи наверо эҷод мекунем, ки ҳеҷ чизи фоиданок намекунад (Thread.Sleep (10000)), аммо дар ҳаёти воқеӣ ин бояд як кори мураккаби пуршиддати CPU бошад.

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
}

Вазифа бо як қатор вариантҳо сохта мешавад:

  • LongRunning як ишораи он аст, ки супориш ба зудӣ иҷро нахоҳад шуд, яъне маънои онро дорад, ки ба назар гирифтан лозим нест, ки ришта аз ҳавз нагиред, балки барои ин вазифа алоҳида эҷод кунед, то ба дигарон зарар нарасонад.
  • AttachedToParent - Вазифаҳоро дар иерархия ҷойгир кардан мумкин аст. Агар ин хосият истифода шуда бошад, пас Вазифа метавонад дар ҳолате бошад, ки худи он ба анҷом расида ва интизори иҷрои фарзандони худ аст.
  • PreferFairness - маънои онро дорад, ки беҳтар аст, ки супоришҳоеро, ки барои иҷро пештар фиристода шудаанд, пеш аз он ки дертар фиристода шаванд, иҷро кунед. Аммо ин танҳо як тавсия аст ва натиҷаҳо кафолат дода намешаванд.

Параметри дуюме, ки ба усул гузашт, CancellationToken аст. Барои дуруст идора кардани бекоркунии амалиёт пас аз оғоз шудани он, рамзи иҷрошаванда бояд бо чекҳои ҳолати CancellationToken пур карда шавад. Агар ягон санҷиш мавҷуд набошад, он гоҳ усули Cancel, ки дар объекти CancellationTokenSource даъват шудааст, метавонад иҷрои супоришро танҳо пеш аз оғози он қатъ кунад.

Параметри охирин объекти банақшагирии навъи TaskScheduler мебошад. Ин синф ва наслҳои он барои назорат кардани стратегияҳои тақсимоти Вазифаҳо дар байни риштаҳо тарҳрезӣ шудаанд; ба таври нобаёнӣ, супориш дар риштаи тасодуфӣ аз ҳавз иҷро карда мешавад.

Оператори интизорӣ ба Вазифаи сохташуда татбиқ карда мешавад, ки ин маънои онро дорад, ки рамзи пас аз он навишташуда, агар мавҷуд бошад, дар ҳамон контекст (аксар вақт ин маънои дар ҳамон риштаро дорад) ҳамчун рамзи пеш аз интизорӣ иҷро карда мешавад.

Усул ҳамчун void асинкӣ қайд карда шудааст, ки маънои онро дорад, ки он метавонад оператори интизориро истифода барад, аммо рамзи занг наметавонад интизори иҷро шавад. Агар чунин хусусият зарур бошад, пас усул бояд Вазифаро баргардонад. Усулҳои беэътибории асинкӣ хеле маъмуланд: чун қоида, инҳо коркардкунандагони рӯйдодҳо ё усулҳои дигаре мебошанд, ки дар оташ кор мекунанд ва принсипро фаромӯш мекунанд. Агар ба шумо лозим аст, ки на танҳо имконият диҳед, ки то охири иҷро интизор шавед, балки натиҷаро баргардонед, пас шумо бояд супоришро истифода баред.

Дар супорише, ки усули StartNew баргардонид ва инчунин дар ҳама гуна дигар, шумо метавонед усули ConfigureAwait-ро бо параметри бардурӯғ даъват кунед, пас иҷроиш пас аз интизорӣ на дар контексти гирифташуда, балки дар контексти худсарона идома меёбад. Ин бояд ҳамеша анҷом дода шавад, вақте ки контексти иҷро барои код пас аз интизорӣ муҳим нест. Ин инчунин тавсияи MS ҳангоми навиштани кодест, ки дар китобхона бастабандӣ карда мешавад.

Биёед каме бештар дар бораи он ки чӣ гуна шумо метавонед анҷоми супоришро интизор шавед. Дар зер намунаи рамз оварда шудааст, ки бо шарҳҳо дар бораи кай интизорӣ ба таври шартӣ хуб иҷро мешавад ва вақте ки он шартан бад иҷро мешавад.

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
}

Дар мисоли аввал, мо интизор мешавем, ки супориш бидуни бастани риштаи занг ба анҷом расад; мо ба коркарди натиҷа танҳо вақте ки он аллакай мавҷуд аст, бармегардем; то он вақт, риштаи даъваткунанда ба дастгоҳи худ мемонад.

Дар варианти дуюм, мо то он даме, ки натиҷаи усул ҳисоб карда нашавад, риштаи зангро маҳкам мекунем. Ин на танҳо аз он сабаб бад аст, ки мо як ришта, чунин як манбаи пурарзиши барномаро бо бекористии оддӣ ишғол кардаем, балки инчунин аз он сабаб аст, ки агар рамзи усуле, ки мо даъват мекунем интизорӣ дошта бошад ва контексти ҳамоҳангсозӣ пас аз он баргаштан ба риштаи даъваткунандаро талаб кунад. интизор шавед, он гоҳ мо ба бунбаст дучор мешавем: Риштаи занг интизор аст, ки натиҷаи усули асинхронӣ ҳисоб карда шавад, усули асинхронӣ беҳуда кӯшиш мекунад, ки иҷрои худро дар риштаи даъваткунанда идома диҳад.

Камбудии дигари ин равиш мушкили коркарди хатоҳост. Гап дар он аст, ки хатогиҳо дар коди асинхронӣ ҳангоми истифодаи асинхронӣ/интизор хеле осонанд - онҳо ҳамон тавре рафтор мекунанд, ки гӯё код синхронӣ бошад. Дар ҳоле ки агар мо экзорцизми синхронии интизориро ба супориш татбиқ кунем, истиснои аслӣ ба AggregateException табдил меёбад, яъне. Барои коркарди истисно, шумо бояд навъи InnerException-ро тафтиш кунед ва худ дар дохили як блоки catch занҷираи if нависед ё ҳангоми сохтан сайдро истифода баред, ба ҷои занҷири блокҳои сайд, ки дар ҷаҳони C# бештар шинос аст.

Намунаҳои сеюм ва ниҳоӣ низ бо ҳамин сабаб бад қайд карда шудаанд ва ҳама мушкилоти якхеларо дар бар мегиранд.

Усулҳои WhenAny ва WhenAll барои интизории як гурӯҳи Вазифаҳо бениҳоят қулай мебошанд; онҳо як гурӯҳи Вазифаҳоро ба як гурӯҳ мепайвандад, ки онҳо ё ҳангоми ба кор андохтани супориш аз гурӯҳ ё вақте ки ҳамаи онҳо иҷрои худро анҷом медиҳанд, оташ мегиранд.

Қатъи риштаҳо

Бо сабабҳои гуногун, мумкин аст, ки пас аз оғоз шудани он ҷараёнро қатъ кунед. Якчанд роҳҳо барои ин кор вуҷуд доранд. Синфи Thread дорои ду усули ба таври мувофиқ номгузорӣ шудааст: Қатъ кардан и Қатъ кардан. Якум барои истифода тавсия дода намешавад, зеро пас аз занг задани он дар ҳар лаҳзаи тасодуфӣ, ҳангоми коркарди ҳама гуна дастур, истисно партофта мешавад ThreadAbortedException. Шумо интизор нестед, ки ҳангоми афзоиш додани ягон тағирёбандаи адад чунин истисно партофта мешавад, дуруст? Ва ҳангоми истифодаи ин усул, ин вазъияти хеле воқеӣ аст. Агар ба шумо лозим ояд, ки CLR аз тавлиди чунин истисно дар қисмати муайяни код пешгирӣ кунед, шумо метавонед онро дар зангҳо печонед Thread.BeginCriticalRegion, Thread.EndCriticalRegion. Ҳар рамзи дар блоки ниҳоии навишташуда дар чунин зангҳо печонида мешавад. Аз ин сабаб, дар умқи рамзи чаҳорчӯба шумо метавонед блокҳоро бо кӯшиши холӣ пайдо кунед, аммо дар ниҳоят холӣ нест. Microsoft ин усулро чунон рӯҳафтода мекунад, ки онҳо онро ба .net core дохил накардаанд.

Усули Interrupt бештар пешгӯишаванда кор мекунад. Он метавонад ба истиснои ришта халал расонад ThreadInterruptedException танҳо дар он лаҳзаҳое, ки ришта дар ҳолати интизорӣ қарор дорад. Он ба ин ҳолат ҳангоми овезон ҳангоми интизории WaitHandle, қулф ё пас аз занг задан ба Thread.Sleep ворид мешавад.

Ҳарду варианти дар боло тавсифшуда аз сабаби пешгӯинашавандаи онҳо бад мебошанд. Роҳи ҳалли он истифодаи сохтор аст CancellationToken ва синф CancellationTokenSource. Гап дар сари он аст: як намунаи синфи CancellationTokenSource сохта шудааст ва танҳо шахсе, ки соҳиби он аст, метавонад бо занги метод амалиётро қатъ кунад. Бекор. Танҳо CancellationToken ба худи амалиёт интиқол дода мешавад. Соҳибони CancellationToken наметавонанд амалиётро худашон бекор кунанд, аммо танҳо метавонанд тафтиш кунанд, ки оё амалиёт бекор карда шудааст. Барои ин моликияти булӣ мавҷуд аст Бекор кардан талаб карда мешавад ва усул ThrowIfCancelRequested. Охирин як истисноро ба вуҷуд меорад TaskCancelledException агар усули Cancel дар мисоли CancellationToken паррот карда шуда бошад. Ва ин усулест, ки ман тавсия медиҳам. Ин беҳбуди нисбат ба имконоти қаблӣ тавассути ба даст овардани назорати пурраи он аст, ки дар кадом лаҳза амалиёти истисноиро қатъ кардан мумкин аст.

Ваҳшиёнатарин вариант барои боздоштани ришта даъват кардани функсияи Win32 API TerminateThread мебошад. Рафтори CLR пас аз занги ин функсия метавонад пешгӯинашаванда бошад. Дар MSDN дар бораи ин функсия инҳо навишта шудаанд: "TerminateThread як функсияи хатарнок аст, ки бояд танҳо дар ҳолатҳои шадидтарин истифода шавад. "

Табдил додани API-и кӯҳна ба вазифаҳо бо истифода аз усули FromAsync

Агар шумо бахти кофӣ дошта бошед, ки дар лоиҳае, ки пас аз ҷорӣ шудани Вазифаҳо оғоз шуда буд ва боиси даҳшати ором барои аксари таҳиягарон нагардад, пас ба шумо лозим нест, ки бо бисёр API-ҳои кӯҳна, ҳам аз ҷониби шахсони сеюм ва ҳам бо гурӯҳи шумо кор кунед. дар гузашта шиканҷа кардааст. Хушбахтона, дастаи .NET Framework дар бораи мо ғамхорӣ кард, гарчанде ки шояд ҳадаф ғамхорӣ кардани худамон буд. Новобаста аз он, ки .NET дорои як қатор асбобҳо барои бедард табдил додани коди дар равишҳои барномасозии асинхронии кӯҳна ба нав навишта шудааст. Яке аз онҳо усули FromAsync аз TaskFactory мебошад. Дар мисоли коди дар поён овардашуда, ман усулҳои кӯҳнаи асинхронии синфи WebRequest-ро бо истифода аз ин усул ба кор меандозам.

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

Ин танҳо як мисол аст ва гумон аст, ки шумо ин корро бо намудҳои дарунсохт иҷро кунед, аммо ҳама лоиҳаи кӯҳна танҳо бо усулҳои BeginDoSomething пур аст, ки усулҳои IAsyncResult ва EndDoSomething-ро бармегардонанд, ки онро қабул мекунанд.

Бо истифода аз синфи TaskCompletionSource API-и кӯҳнаро ба Task Based табдил диҳед

Боз як воситаи муҳиме, ки бояд баррасӣ шавад, синф аст Сарчашмаи анҷоми супориш. Аз нуқтаи назари функсияҳо, мақсад ва принсипи кор, он метавонад то андозае усули RegisterWaitForSingleObject синфи ThreadPool-ро ба хотир орад, ки ман дар бораи он дар боло навишта будам. Бо истифода аз ин синф, шумо метавонед ба осонӣ ва ба осонӣ API-ҳои асинхронии кӯҳнаро дар Tasks печонед.

Шумо мегӯед, ки ман аллакай дар бораи усули FromAsync синфи TaskFactory, ки барои ин мақсадҳо пешбинӣ шудааст, сӯҳбат кардам. Дар ин ҷо мо бояд тамоми таърихи таҳияи моделҳои асинхронӣ дар .net, ки Microsoft дар тӯли 15 соли охир пешниҳод кардааст, ба ёд орем: пеш аз пайдоиши намунаи асинхронии ба вазифаҳо асосёфта (TAP), намунаи барномасозии асинхронӣ (APP) мавҷуд буд, ки дар бораи усулхо буд ОғозDoSomething бармегардад IAsyncResult ва усулхо ПоёнDoSomething, ки онро қабул мекунад ва барои мероси ин солҳо усули FromAsync комилан комил аст, аммо бо гузашти вақт он бо Намунаи асинхронӣ дар асоси рӯйдодҳо иваз карда шуд (ВА АП), ки тахмин мекард, ки ҳодиса ҳангоми анҷоми амалиёти асинхронӣ ба миён меояд.

TaskCompletionSource барои печонидани Вазифаҳо ва API-ҳои кӯҳна, ки дар атрофи модели рӯйдод сохта шудаанд, комил аст. Мохияти кори он аз ин иборат аст: объекти ин класс дорои моликияти умумии типи Task мебошад, ки холати онро тавассути усулхои SetResult, SetException ва г. класси TaskCompletionSource идора кардан мумкин аст. Дар ҷойҳое, ки оператори интизорӣ ба ин Вазифа истифода шудааст, он вобаста ба усули ба TaskCompletionSource татбиқшаванда ба истиснои ҳолат иҷро мешавад ё ноком мешавад. Агар он то ҳол равшан набошад, биёед ин мисоли кодро бубинем, ки дар он баъзе API-и кӯҳнаи EAP дар супориш бо истифода аз TaskCompletionSource печонида шудааст: вақте ки ҳодиса сар мезанад, супориш ба ҳолати анҷомёфта интиқол дода мешавад ва усуле, ки оператори интизориро истифода кардааст ба ин вазифадо баъди гирифтани объектдо ичрои онро давом медидад натиҷа.

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

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

    result completionSource.Task;
}

Маслиҳатҳо ва ҳилаҳо

Пӯшидани API-ҳои кӯҳна на ҳама чизест, ки бо истифода аз TaskCompletionSource анҷом дода мешавад. Истифодаи ин синф имкони ҷолиби тарҳрезии API-ҳои гуногунро дар вазифаҳое, ки риштаҳоро ишғол намекунанд, мекушояд. Ва ҷараён, тавре ки мо дар ёд дорем, як манбаи гаронбаҳост ва шумораи онҳо маҳдуд аст (асосан бо миқдори RAM). Ин маҳдудиятро тавассути таҳияи, масалан, як веб-барномаи пурбор бо мантиқи мураккаби тиҷорат ба осонӣ ба даст овардан мумкин аст. Биёед, имко-ниятхоеро, ки ман дар бораи он сухан меронам, хангоми ба амал баровардани чунин найранг, монанди «Поллинги Long-Pulling» ба назар гирем.

Хулоса, моҳияти ҳилла аз ин иборат аст: шумо бояд аз API дар бораи баъзе воқеаҳое, ки дар паҳлӯи он рух медиҳанд, маълумот гиред, дар ҳоле ки API бо баъзе сабабҳо дар бораи ҳодиса хабар дода наметавонад, балки танҳо ҳолати онро баргардонад. Намунаи ин ҳама APIҳо мебошанд, ки дар болои HTTP пеш аз замони WebSocket сохта шудаанд ё вақте ки бо ягон сабаб истифодаи ин технология имконнопазир буд. Мизоҷ метавонад аз сервери HTTP пурсад. Сервери HTTP худаш алоқаро бо муштарӣ оғоз карда наметавонад. Як ҳалли оддӣ пурсиши сервер бо истифода аз таймер аст, аммо ин як бори иловагӣ дар сервер ва таъхири иловагиро ба ҳисоби миёна TimerInterval / 2 эҷод мекунад. Барои бартараф кардани ин ҳиллае бо номи Long Polling ихтироъ карда шуд, ки он таъхири посухро дар бар мегирад. сервер то ба охир расидани мӯҳлат ё ҳодиса рӯй диҳад. Агар ҳодиса рух дода бошад, пас он коркард карда мешавад, агар не, дархост дубора фиристода мешавад.

while(!eventOccures && !timeoutExceeded)  {

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

Аммо чунин ҳалли даҳшатнок ба зудӣ собит хоҳад шуд, ки шумораи муштариёни интизори чорабинӣ зиёд мешавад, зеро... Ҳар як муштарӣ як риштаи пурраро ишғол мекунад, ки интизори ҳодиса аст. Бале, ва вақте ки ин ҳодиса оғоз мешавад, мо 1ms таъхири иловагӣ мегирем, аксар вақт ин аҳамият надорад, аммо чаро нармафзорро аз он метавонад бадтар кунад? Агар мо Thread.Sleep(1)-ро хориҷ кунем, он гоҳ беҳуда як ядрои протсессорро 100% бекор, дар як давраи бефоида давр мезанем. Бо истифода аз TaskCompletionSource шумо метавонед ин кодро ба осонӣ аз нав созед ва ҳамаи мушкилоти дар боло зикршударо ҳал кунед:

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

Ин код барои истеҳсолот омода нест, балки танҳо намоишӣ аст. Барои истифодаи он дар ҳолатҳои воқеӣ, шумо инчунин бояд ҳадди аққал вазъиятро ҳал кунед, вақте ки паём дар замоне мерасад, ки ҳеҷ кас онро интизор нест: дар ин ҳолат, усули AsseptMessageAsync бояд Вазифаи аллакай анҷомшударо баргардонад. Агар ин маъмултарин ҳолат бошад, пас шумо метавонед дар бораи истифодаи ValueTask фикр кунед.

Вақте ки мо дархост барои паём мегирем, мо TaskCompletionSource-ро дар луғат эҷод мекунем ва ҷойгир мекунем ва он гоҳ интизор мешавем, ки аввал чӣ мешавад: фосилаи вақти муайяншуда тамом мешавад ё паём қабул мешавад.

ValueTask: чаро ва чӣ тавр

Операторҳои асинх/интизорӣ, ба монанди оператори баргардонидани ҳосил, аз метод як мошини ҳолатиро тавлид мекунанд ва ин эҷоди объекти нав аст, ки қариб ҳамеша муҳим нест, аммо дар ҳолатҳои кам он метавонад мушкилот эҷод кунад. Ин ҳолат метавонад як усуле бошад, ки воқеан зуд-зуд даъват карда мешавад, сухан дар бораи даҳҳо ва садҳо ҳазорҳо дар як сония меравад. Агар чунин усул тавре навишта шуда бошад, ки дар аксари мавридҳо он натиҷаро пас аз ҳамаи усулҳои интизорӣ баргардонад, пас .NET асбоби оптимизатсияи ин - сохтори ValueTask -ро пешкаш мекунад. Барои равшан кардани он, биёед як мисоли истифодаи онро дида бароем: кэш мавҷуд аст, ки мо зуд-зуд ба он меравем. Дар он баъзе арзишҳо мавҷуданд ва мо онҳоро танҳо бармегардонем; агар не, мо барои ба даст овардани онҳо ба ягон IO суст меравем. Ман мехоҳам охиринро ба таври асинхронӣ иҷро кунам, ин маънои онро дорад, ки тамоми усул асинхронӣ мешавад. Ҳамин тариқ, роҳи равшани навиштани усул чунин аст:

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

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

Аз сабаби хоҳиши каме оптимизатсия ва тарси андаке аз он, ки Рослин ҳангоми тартиб додани ин код чӣ тавлид мекунад, шумо метавонед ин мисолро ба таври зерин нависед:

public Task<string> GetById(int id) {

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

Воқеан, ҳалли оптималӣ дар ин ҳолат оптимизатсияи роҳи гарм, яъне гирифтани арзиш аз луғат бе ягон тақсимоти нолозим ва бор кардан ба GC хоҳад буд, дар ҳоле ки дар ҳолатҳои нодире, ки мо то ҳол барои маълумот ба IO рафтан лозим аст. , ҳама чиз ҳамчун плюс / минус бо усули кӯҳна боқӣ мемонад:

public ValueTask<string> GetById(int id) {

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

Биёед ба ин порчаи код бодиққат назар андозем: агар дар кэш арзиш мавҷуд бошад, мо сохтор эҷод мекунем, вагарна вазифаи воқеӣ дар як чизи пурмазмун печонида мешавад. Рамзи зангзананда парвое надорад, ки ин код дар кадом роҳ иҷро шудааст: ValueTask, аз нуқтаи назари синтаксиси C#, дар ин ҳолат мисли вазифаи муқаррарӣ рафтор хоҳад кард.

TaskSchedulers: идоракунии стратегияҳои оғози вазифаҳо

API-и навбатӣ, ки ман мехоҳам онро баррасӣ кунам, ин синф аст Вазифаҳо ва ҳосилаҳои он. Ман аллакай дар боло зикр кардам, ки TPL дорои қобилияти идоракунии стратегияҳои паҳнкунии Вазифаҳо дар байни риштаҳо мебошад. Чунин стратегияҳо дар наслҳои синфи TaskScheduler муайян карда мешаванд. Қариб ҳама стратегияҳоеро, ки ба шумо лозим аст, дар китобхона пайдо кардан мумкин аст. Extras ParallelExtensions, аз ҷониби Microsoft таҳия шудааст, аммо қисми .NET нест, аммо ҳамчун бастаи Nuget дода мешавад. Биёед ба баъзе аз онҳо мухтасар назар андозем:

  • CurrentThreadTaskScheduler — супоришҳоро дар риштаи ҷорӣ иҷро мекунад
  • LimitedConcurrencyLevelTaskScheduler — шумораи Вазифаҳои дар як вақт иҷрошавандаро аз рӯи параметри N, ки дар конструктор қабул карда мешавад, маҳдуд мекунад
  • Тартиб додашудаи TaskScheduler — ҳамчун LimitedConcurrencyLevelTaskScheduler(1) муайян шудааст, бинобар ин вазифаҳо пайдарпай иҷро мешаванд.
  • WorkStealingTaskScheduler - асбобҳо кор-дуздй муносибати тақсимоти вазифаҳо. Аслан он як ThreadPool алоҳида аст. Масъалаеро ҳал мекунад, ки дар .NET ThreadPool як синфи статикӣ барои ҳама замимаҳо мебошад, ки маънои изофабории он ё истифодаи нодурусти он дар як қисми барнома метавонад дар қисми дигар боиси таъсири тараф гардад. Гузашта аз ин, фаҳмидани сабаби ин гуна камбудиҳо ниҳоят душвор аст. ки. Дар қисматҳои барнома, ки истифодаи ThreadPool метавонад хашмгин ва пешгӯинашаванда бошад, шояд зарурати истифодаи WorkStealingTaskSchedulers алоҳида вуҷуд дошта бошад.
  • Банавбат гузоштани TaskScheduler — ба шумо имкон медихад, ки супоришхоро аз руи коидахои навбати афзалиятнок ичро кунед
  • ThreadPerTaskScheduler — барои хар як супорише, ки дар он ичро карда мешавад, риштаи алохида месозад. Метавонад барои корҳое муфид бошад, ки барои анҷоми он муддати пешгӯинашавандаи тӯлонӣ лозим аст.

Тафсилоти хуб вуҷуд дорад мақола дар бораи TaskSchedulers дар блоги Microsoft.

Барои ислоҳи муносиби ҳама чизҳои марбут ба Вазифаҳо, Visual Studio дорои равзанаи Вазифаҳо мебошад. Дар ин равзана шумо метавонед ҳолати кунунии супоришро бинед ва ба сатри иҷрошавандаи код гузаред.

.NET: Асбобҳо барои кор бо чанд ришта ва асинхронӣ. Қисми 1

PLinq ва синфи параллелӣ

Илова ба Вазифаҳо ва ҳар чизе, ки дар бораи онҳо гуфта шудааст, дар .NET боз ду асбоби ҷолиб вуҷуд дорад: PLinq (Linq2Parallel) ва синфи Parallel. Якум иҷрои мувозии ҳама амалҳои Linqро дар риштаҳои сершумор ваъда медиҳад. Шумораи риштаҳоро метавон бо истифода аз усули тамдиди WithDegreeOfParallelizm танзим кард. Мутаассифона, аксар вақт PLinq дар реҷаи пешфарзии худ маълумоти кофӣ дар бораи дохили манбаи маълумоти шумо надорад, то суръати назаррасро таъмин кунад, аз тарафи дигар, арзиши кӯшиш хеле паст аст: шумо танҳо бояд пеш аз ин ба усули AsParallel занг занед. занҷири усулҳои Linq ва санҷишҳои иҷроишро иҷро кунед. Ғайр аз он, бо истифода аз механизми Partitions ба PLinq дар бораи хусусияти манбаи маълумоти шумо маълумоти иловагӣ додан мумкин аст. Шумо метавонед бештар хонед дар ин ҷо и дар ин ҷо.

Синфи статикии параллелӣ усулҳоро барои такрор кардан тавассути коллексияи Foreach дар баробари мувозӣ, иҷро кардани даври For ва иҷро кардани вакилони сершумор дар мувозӣ Invoke таъмин мекунад. Иҷрои риштаи ҷорӣ то анҷоми ҳисобҳо қатъ карда мешавад. Миқдори риштаҳоро бо роҳи гузаштани ParallelOptions ҳамчун далели охирин танзим кардан мумкин аст. Шумо инчунин метавонед бо истифода аз имконоти TaskScheduler ва CancellationToken-ро муайян кунед.

натиҷаҳои

Вақте ки ман ба навиштани ин мақола дар асоси маводи гузориши худ ва маълумоте, ки дар давоми корам баъд аз он ҷамъоварӣ кардаам, оғоз кардам, интизор набудам, ки ин қадар зиёд мешавад. Ҳоло, вақте ки муҳаррири матние, ки ман ин мақоларо менависам, ба ман сарзаниш мекунад, ки саҳифаи 15 рафтааст, ман натиҷаҳои мобайнӣ ҷамъбаст мекунам. Дигар ҳилаҳо, APIҳо, асбобҳои визуалӣ ва домҳо дар мақолаи навбатӣ баррасӣ карда мешаванд.

Натиҷаҳо:

  • Шумо бояд асбобҳои кор бо риштаҳо, асинхронӣ ва параллелизмро донед, то аз захираҳои компютерҳои компютерии ҳозиразамон истифода баред.
  • .NET барои ин мақсадҳо асбобҳои гуногун дорад
  • На ҳамаи онҳо якбора пайдо шуданд, аз ин рӯ шумо аксар вақт чизҳои кӯҳнаро пайдо карда метавонед, аммо роҳҳои табдил додани API-ҳои кӯҳна бе кӯшиши зиёд мавҷуданд.
  • Кор бо риштаҳо дар .NET бо синфҳои Thread ва ThreadPool муаррифӣ мешавад
  • Усулҳои Thread.Abort, Thread.Interrupt ва Win32 API TerminateThread хатарноканд ва барои истифода тавсия дода намешаванд. Ба ҷои ин, беҳтар аст, ки механизми CancellationToken -ро истифода баред
  • Ҷараён захираи арзишманд аст ва таъминоти он маҳдуд аст. Аз ҳолатҳое, ки риштаҳо бо интизории рӯйдодҳо банд ҳастанд, бояд пешгирӣ карда шавад. Барои ин истифодаи синфи TaskCompletionSource қулай аст
  • Воситаҳои пурқувват ва пешрафтаи .NET барои кор бо параллелизм ва асинхронӣ Вазифаҳо мебошанд.
  • Операторҳои c# async/await консепсияи интизории бастанашавандаро амалӣ мекунанд
  • Шумо метавонед тақсимоти Вазифаҳоро дар байни риштаҳо бо истифода аз синфҳои аз TaskScheduler гирифташуда назорат кунед
  • Сохтори ValueTask метавонад дар оптимизатсияи роҳҳои гарм ва трафики хотира муфид бошад
  • Равзанаҳои Visual Studio Tasks and Threads маълумоти зиёдеро барои ислоҳи коди бисёр ришта ё асинхронӣ муфид медиҳанд
  • PLinq як воситаи олиҷаноб аст, аммо он метавонад дар бораи манбаи маълумоти шумо маълумоти кофӣ надошта бошад, аммо онро бо истифода аз механизми тақсимкунӣ ислоҳ кардан мумкин аст.
  • Давом дорад…

Манбаъ: will.com

Илова Эзоҳ