Vlákna v C# - 7. díl
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Vlákna v C# - 7. dílVlákna v C# - 7. díl

 

Vlákna v C# - 7. díl

Google       Google       19. 10. 2008       52 875×

Tentokrát probereme jednu z klíčových věcí – přístup k ovládacímu prvku formuláře pomocí Control.Invoke a BackgroundWorkeru. Na začátek se ale podíváme na věc známou pod názvem apartments.

Oddělení (apartments)

Existují tzv. modely apartmentů (oddělení), úzce spjaté s COM (velmi zjednodušeně řečeno je to předchůdce .NET frameworku). Od takovýchto modelů se sice .NET snaží oprostit, ale někdy je můžete potřebovat, hlavně při práci se staršími API (mezi něž se tedy COM řadí). Ve skutečnosti najdou využití častěji, než se může na první pohled zdát. Vždyť WinForms používají z velké části obalené Win32 API!

Apartment je logický „kontejner“ pro vlákna. Existují dva druhy – „single“ a „multi“. První jmenovaný obsahuje vždy jen jedno vlákno (MTA).

Kromě samotných vláken obsahují apartmenty i objekty. Když dojde k vytvoření objektu uvnitř apartmentu, zůstane v něm po celou dobu jeho životnosti. V tomto se modely apartmentů podobají synchronizačním kontextům s tím rozdílem, že kontexty neobsahují vlákna. V kontextech může jakékoliv vlákno přistupovat k objektu v kontextu, ale k objektu v apartmentu může přistupovat jen vlákno ze stejného apartmentu.

Opět si vysvětlíme význam apartmentů na příkladu „ze života“. Představte si knihovnu, kde každá kniha reprezentuje objekt. Půjčování knih domů z knihovny není dovoleno – kniha (objekt) v ní zůstane po celou dobu života knihovny. Řekněme, že člověk, který do knihovny vstoupí, je vlákno.

Kdyby knihovna fungovala jako synchronizační kontext, mohl by dovnitř vstoupit jen jeden člověk najednou. Pokud by jich bylo víc, před vchodem by se začala tvořit fronta.

V apartmentové knihovně pracují knihovníci – jeden knihovník pro STA knihovnu, celý tým knihovníků pro MTA. Do knihovny nemůže vstoupit žádný cizí člověk – zákazník, který chce nějakou knihu, musí nejdřív signalizovat knihovníka (tomuto procesu se říká „marshalling“). Marshalling probíhá automaticky, ve WinForms funguje tak, že tento mechanismus neustále kontroluje vstupy z klávesnice i myši. Pokud jednotlivé zprávy o těchto událostech přijdou moc rychle za sebou, zařadí se do fronty a vykonají se v pořadí, ve kterém přišly.

Nastavení apartmentu

Vláknu je automaticky přiřazen apartment. Výchozí situace je ta, že dostane multi-threaded apartment, pokud explicitně neřekneme, že chceme STA, jako třeba takto:

Thread t = new Thread (...);
t.SetApartmentState (ApartmentState.STA);

Můžeme bez obtíží přikázat, aby i hlavní vlákno bylo v STA, což se udělá pomocí atributu [STAThread]:

class Program {
  [STAThread]
  static void Main() {
  ...

Slušelo by se napsat pár řádků o využitelnosti apartmentů v praxi. Nemají žádný efekt, pokud s nimi spouštíte čistý .NET kód. Když dva STA kódy zavolají metodu na stejný objekt, nenastane žádný automatický locking, marshalling ani nic podobného. Prostě jako kdybyste spouštěli obyčejný kód. Jen u spouštění unmanaged kódu se jejich síla může projevit.

Typy v namespace System.Window.Forms často využívají původní Win32 kód dělaný pro běh v STA. Kvůli tomu by měla mít aplikace využívající WinForms atribut [STAThread] u své Main metody, jinak by mohlo dojít k pádu aplikace.

Control.Invoke

Ve vícevláknových aplikacích není povolené zavolat metodu nebo vlastnost na ovládací prvek jiným vláknem než tím, které daný prvek vytvořilo. Každý, kdo někdy začal ve svých programech využívat vlákna, se určitě velmi brzo s tímto problémem setkal. Řešením jsou právě metody Control.Invoke a Control.BeginInvoke, pomocí kterých můžete přesměrovat volání metody z jednoho vlákna na „autorské“ vlákno prvku. Nemůžeme se totiž spoléhat na automatický marshalling zmíněný výše, protože k němu dojde jen tehdy, dostane-li se aplikace do unmanaged kódu. Než k tomu dojde, bude už nejspíše pozdě.

WPF je podobné Windows Forms v tom, že ovládací prvky jsou přístupné jen z vlákna, které je vytvořilo. Ekvivalent k Control.Invoke je ve WPF Dispatcher.Invoke.

Dalším perfektním řešením je známý BackgroundWorker. Mocná třída, která obaluje pracovní vlákna a mimo jiné volá automaticky podle potřeby Control.Invoke.

BackgroundWorker

BackgroundWorker je pomocná třída v namespace System.ComponentModel pro správu pracovních vláken. Poskytuje následující věci:

  • Vlastnost „Cancel“ pro signalizování vláknu, aby skončilo i bez volání metody Abort
  • Protokol pro podávání zpráv o průběhu práce, dokončení a zrušení práce
  • Implementace rozhraní IComponent, které BackgroundWorkeru dovoluje, aby se s ním dalo pracovat ve Visual Studio Designeru
  • Zachycování výjimek na pracovním vláknu
  • Schopnost aktualizovat WinForms (i WPF) ovládací prvky na základně průběhu vlákna

Poslední dva body jsou nejspíš ty nejužitečnější – nemusíte ve svých metodách spouštěných pomocí BW používat try/catch bloky a můžete WinForms a WPF upravovat i bez volání Control.Invoke.

BW využívá tzv. fond vláken (thread-pool), který spravuje vytvořená vlákna a sám ukončuje jejich práci. Z tohoto důvodu byste nikdy neměli na BackgroundWorker vlákno volat metodu Abort, o to se sám postará fond vláken.

Následují některé kroky, které musíte podstoupit, abyste mohli ve své aplikaci využít BackgroundWorker:

  • Vytvořit instanci třídy BackgroundWorker a vytvořil handler pro událost DoWork
  • Zavolat metodu RunWorkerAsync (nastartuje instanci BW)

Jako argument metody RunWorkerAsync můžete zadat cokoliv, přijímá totiž typ object. Zadaný argument se následně předá zpracovateli události DoWork:

class Program
{
    static BackgroundWorker bw = new BackgroundWorker();
    static void Main()
    {
        bw.DoWork += bw_DoWork;
        bw.RunWorkerAsync("Zpráva pro DoWork");
        Console.ReadLine();
    }

    static void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        // Obsah této metody je volán na pracovním vláknu
        Console.WriteLine(e.Argument);  // Vypíše „Zpráva pro DoWork“
        // Další kód…
    }
}

Kromě DoWork najdete v BackgroundWorkeru událost RunWorkerCompleted, která se vypálí, když DoWork dokončí svojí práci. Zpracování této události není povinné, ovšem většinou je to užitečné, můžete například zpracovat výjimky, které vzniknou během DoWork. Navíc, zpracovatel této události může přímo přistupovat k WinForms a WPF prvkům bez explicitního marshallingu, zatímco DoWork nemůže.

Pro přidání podpory pro ohlašování pokroku práce musíme udělat následující:

  • Nastavit vlastnost WorkerReportsProgress na true
  • Zevnitř DoWork handleru volat metodu ReportProgress s hodnotou, která určuje procentuální splnění práce
  • Vytvořit zpracovatele události ProgressChanged, který se bude dotazovat na hodnotu vlastnosti ProgressPercentage (v ní je právě uložená hodnota, kterou jsme předali metodě ReportProgress)

Kód uvnitř ProgressChanged zpracovatele může, stejně jako RunWorkerCompleted, volně komunikovat s ovládacími prvky. Toto je místo, které se často využívá k aktualizování ProgressBaru.

Přidání podpory pro ukončení práce vlákna zase přidáme takhle:

  • Nastavte vlastnost WorkerSupportsCancellation na true
  • Zevnitř DoWork handleru sledujte stav vlastnosti CancellationPending. Pokud je její hodnota true, nastavte argument „e“ u DoWork na Cancel = true; (jistě, že můžete nastavit e.Cancel na true i bez kontrolování CancellationPending)
  • Zavolejte CancelAsync pro ukočení práce

Následující příklad ukazuje vše, co jsme si výše popsali (v kódu jsou využity inicializátory objektů):

using System;
using System.Threading;
using System.ComponentModel;

class Program
{
    static BackgroundWorker bw;
    static void Main()
    {
        bw = new BackgroundWorker {WorkerReportsProgress = true, WorkerSupportsCancellation = true};
        bw.DoWork += bw_DoWork;
        bw.ProgressChanged += bw_ProgressChanged;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;

        bw.RunWorkerAsync("Zdravíme pracovní vlákno!");

        Console.WriteLine("Stiskněte Enter v následujících 5 vteřinách pro ukončení");
        Console.ReadLine();
        if (bw.IsBusy) bw.CancelAsync();
        Console.ReadLine();
    }

    static void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 0; i <= 100; i += 20)
        {
            if (bw.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
            bw.ReportProgress(i);
            Thread.Sleep(1000);
        }
        e.Result = 123;    // Tato hodnota se předá do RunWorkerCompleted
    }

    static void bw_RunWorkerCompleted(object sender,
    RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
            Console.WriteLine("Zrušili jste práci");
        else if (e.Error != null)
            Console.WriteLine("Vyskytla se výjimka: " + e.Error);
        else
            Console.WriteLine("Vše hotovo – " + e.Result);      // Hodnota, kterou jsme předali na konci DoWork
    }

    static void bw_ProgressChanged(object sender,
    ProgressChangedEventArgs e)
    {
        Console.WriteLine("Hotovo procent " + e.ProgressPercentage + "%");
    }
}

Odvozování od BackgroundWorker

Třída BackgroundWorker není sealed (takže od ní můžeme odvozovat další třídy) a poskytuje virtuální metodu OnDoWork. Díky tomu, pokud píšeme metodu, jejíž průběh by mohl dlouho trvat (např. načítání nějaké databáze), můžeme vrátit instanci třídy odvozené od BackgroundWorkeru, která je tím pádem už předpřipravená na asynchronní operace. Jednoduché řešení, které ani nezabere příliš času na implementaci:

public class Client
{
    public FinancialWorker GetFinancialTotalsBackground(int foo, int bar)
    {
        return new FinancialWorker(foo, bar);
    }
}

public class FinancialWorker : BackgroundWorker
{
    public Dictionary<string, int> Result;
    public volatile int Foo, Bar; // I k vysvětlení volatile
    public FinancialWorker() // se jednou dostaneme :-)
    {
        WorkerReportsProgress = true;
        WorkerSupportsCancellation = true;
    }

    public FinancialWorker(int foo, int bar)
        : this()
    {
        Foo = foo;
        Bar = bar;
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        ReportProgress(0, "Právě jsem začal pracovat!");

        // ... finished_report indikuje,
        // jestli je ještě co dělat
        while (!finished_report )
        {
            if (CancellationPending)
            {
                e.Cancel = true;
                return;
            }

            // ... tady by mělo být spočítání průběhu v %
            // uložené jako percentCompleteCalc
            ReportProgress(percentCompleteCalc, "Už to skoro bude...");
        }
        ReportProgress(100, "Hotovo!");
        e.Result = Result;
        // Zpracovat data...
    }
}

Když dojde k zavolání metody GetFinancialTotalsBackground, dostanete nový objekt typu FinancialWorker. Je schopný zpracovávat operace na pozadí, může oznamovat svůj průběh, může být ukončen a je kompatibilní s WinForms i bez (přímého) použití Control.Invoke.

A je to! Dnes jsme se konečně dostali k věci, kterou dokážete jistě využít v jakékoliv multi-threaded aplikaci. Příště nás čekají třídy ReaderWriterLockSlim (novinka v .NET 3.5) a ReaderWriterLock.

Zdroj: http://www.albahari.com/threading/part3.html#_Apartments_and_Windows

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

3 názory  —  3 nové  
Hlasování bylo ukončeno    
0 hlasů
Google
Jakub studuje informatiku na FIT ČVUT, jeho oblíbenou platformou je .NET.
Web     Twitter     Facebook     LinkedIn    

Nové články

Obrázek ke článku Hybridní inteligentní systémy 2

Hybridní inteligentní systémy 2

V technické praxi využíváme často kombinaci různých disciplín umělé inteligence a klasických výpočtů. Takovým systémům říkáme hybridní systémy. V tomto článku se zmíním o určitém typu hybridního systému, který je užitečný ve velmi složitých výrobních procesech.

Obrázek ke článku Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Vedení týmu v oboru informačních technologií se nijak zvlášť neliší od jiných oborů. Přesto však IT manažeři čelí výzvě v podobě velmi rychlého rozvoje a tím i rostoucími nároky na své lidi. Udržet pozornost, motivaci a efektivitu týmu vyžaduje opravdu pevné manažerské základy a zároveň otevřenost a flexibilitu pro stále nové výzvy.

Obrázek ke článku Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Za poslední rok se podoba práce zaměstnanců změnila k nepoznání. Především plošné zavedení home office, které mělo být zpočátku jen dočasným opatřením, je pro mnohé už více než rok každodenní realitou. Co ale dělat, když se při práci z domova ztrácí motivace, zaměstnanci přestávají komunikovat a dříve fungující tým se rozpadá na skupinu solitérů? Odborníci na personalistiku dali dohromady několik rad, jak udržet tým v chodu, i když pracovní podmínky nejsou ideální.

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý