Správa asynchronních a paralelních úloh

Dokončeno

Pro vývojáře v jazyce C# poskytuje knihovna TPL (Task Parallel Library) jednodušší způsob psaní paralelního kódu. Ne všechny kódy jsou ale vhodné pro paralelizaci. Pokud například smyčka provádí při každé iteraci jen malou část práce nebo neproběhne mnoho iterací, mohou náklady na paralelizaci způsobit, že kód poběží pomaleji. Paralelizace, stejně jako jakýkoliv vícevláknový kód, navíc zvyšuje složitost provádění programu.

Běžné nástrahy v datech a paralelismu úkolů

V mnoha případech mohou Parallel.For a Parallel.ForEach poskytnout významné zlepšení výkonu oproti běžným sekvenčním smyčkám. Práce paralelizace smyčky ale přináší složitost, která může vést k problémům, které nejsou v sekvenčním kódu tak běžné.

Nepředpokládej, že paralelní zpracování je vždy rychlejší než sekvenční.

V některých případech může paralelní smyčka běžet pomaleji než její sekvenční ekvivalent. Základním pravidlem je, že paralelní smyčky s malým počtem iterací a rychlými uživatelskými delegáty pravděpodobně příliš neurychlí. Vzhledem k tomu, že výkon se týká mnoha faktorů, doporučujeme vždy měřit skutečné výsledky.

Vyhněte se zápisu do umístění sdílené paměti

V sekvenčním kódu není neobvyklé číst ze statických proměnných nebo pole třídy nebo do nich zapisovat. Avšak, kdykoli k těmto proměnným přistupuje více vláken současně, existuje významný potenciál pro závodní podmínky. I když můžete použít zámky k synchronizaci přístupu k proměnné, náklady na synchronizaci můžou poškodit výkon. Proto doporučujeme, abyste se co nejvíce vyhnuli nebo omezili přístup ke sdílenému stavu v paralelní smyčce. Nejlepším způsobem, jak to provést, je použít přetížení Parallel.For a Parallel.ForEach, která využívají proměnnou System.Threading.ThreadLocal<T> k uložení vláknově lokálního stavu během provádění cyklu.

Vyhněte se nadměrné paralelizaci

Při používání paralelních smyček vám vznikají režijní náklady na dělení zdrojové kolekce a synchronizaci pracovních vláken. Výhody paralelizace jsou dále omezeny počtem procesorů v počítači. Není možné získat žádné zrychlení spuštěním několika vláken vázaných na výpočetní výkon pouze na jednom procesoru. Proto musíte být opatrní, abyste příliš neparalelizovali smyčku.

Nejběžnějším scénářem, ve kterém může dojít k nadměrné paralelizaci, je ve vnořených smyčkách. Ve většině případů je nejlepší paralelizovat pouze vnější smyčku, pokud neplatí jedna nebo více následujících podmínek:

  • Vnitřní smyčka je známá tím, že je dlouhá.
  • Provádíte nákladný výpočet pro každou objednávku.
  • V cílovém systému je známo, že má dostatek procesorů pro zpracování počtu vláken, která jsou vytvořena paralelizací zpracování.

Nejlepší způsob, jak určit optimální tvar dotazu, je ve všech případech testovat a měřit.

Zpracování výjimek v asynchronních a paralelních úlohách

Pokud ke spouštění úloh použijete knihovnu TPL (Task Parallel Library), mohou k výjimkám dojít několika různými způsoby. Nejběžnější je, když úloha vyvolá výjimku. Vyvolání výjimky může nastat, když úloha běží ve vlákně fondu vláken nebo když běží v hlavním vlákně. V obou případech se výjimka rozšíří zpět do volajícího vlákna.

Když použijete metodu Task.Wait k čekání na dokončení úkolu, všechny výjimky vyvolané úlohou se rozšíří zpět do volajícího vlákna. Tyto výjimky můžete zpracovat pomocí bloku try/catch. Pokud je úkol nadřazený připojeným podřízeným úkolům nebo pokud čekáte na více úkolů, může dojít k vyvolání několika výjimek. Pokud dojde k vyvolání jedné nebo více výjimek, zabalí se do AggregateException instance.

Výjimka AggregateException má vlastnost InnerExceptions, kterou lze procházet pomocí enumerace, aby se prozkoumaly všechny původní výjimky, které byly vyvolány, a každá mohla být individuálně zpracována (nebo nezpracována).

Následující příklad ukazuje, jak zpracovat výjimky vyvolané úlohou.


public static partial class Program
{
    public static void Main()
    {
        HandleThree();
    }
    
    public static void HandleThree()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                // Rethrow any other exception.
                else
                {
                    throw ex;
                }
            }
        }
    }
}

// Define the CustomException class
public class CustomException : Exception
{
    public CustomException(string message) : base(message) { }
}
// The example displays the following output:
//        This exception is expected!

V tomto příkladu HandleThree metoda vytvoří úlohu, která vyvolá CustomException. Blok try/catch zachytí AggregateException a prochází kolekcí InnerExceptions. Pokud je výjimka typu CustomException, vytiskne zprávu do konzoly. Pokud se jedná o jakýkoli jiný typ výjimky, znovu ho zvětšuje.

Původní výjimky můžete zpracovat také pomocí AggregateException.Handle metody. Tato metoda přebírá delegáta, který je volána pro každou výjimku v kolekci InnerExceptions . Pokud delegát vrátí hodnotu true, výjimka je považována za zpracovanou a je odebrána z kolekce. Pokud vrátí hodnotu false, výjimka se znovu vyvolá.

Následující příklad ukazuje, jak použít metodu Handle pro zpracování výjimek vyvolaných úlohou.


public static partial class Program
{
    public static void HandleFour()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            ae.Handle(ex =>
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                    return true;
                }
                // Rethrow any other exception.
                return false;
            });
        }
    }
}

V tomto příkladu HandleFour metoda vytvoří úlohu, která vyvolá CustomException. Blok try/catch zachytí metodu AggregateException a zavolá ji Handle . Delegát zkontroluje, zda je výjimka typu CustomException. Pokud je výjimka typu CustomException, delegát vytiskne zprávu do konzoly a vrátí true. Odpověď true značí, že výjimka byla zpracována. Pokud je výjimka jiným typem výjimky, delegát vrátí false, což způsobí, že se výjimka znovu obnoví.

Shrnutí

Tato lekce popisuje situace, kdy kód není vhodný pro paralelizaci, a popisuje běžné nástrahy v datech a paralelismu úkolů. Předpokládejme například, že paralelní zpracování je vždy rychlejší, zápis do sdílených paměťových míst a nadměrná paralelizace. Obsah také vysvětluje, jak zpracovávat výjimky v asynchronních a paralelních úlohách, včetně toho, jak používat metodu Task.Wait a metodu AggregateException.Handle .

Klíčové body

  • Ne všechny kódy jsou vhodné pro paralelizaci. Testování a měření výkonu je nezbytné před paralelizací kódu.
  • Mezi běžné nástrahy v paralelismu dat a úkolů patří předpoklad, že paralelní zpracování je vždy rychlejší, zápis do sdílených paměťových prostorů a nadměrná paralelizace.
  • Výjimky v asynchronních a paralelních úlohách je možné zpracovat pomocí Task.Wait metody a AggregateException.Handle metody.
  • Výjimka AggregateException má vlastnost InnerExceptions, kterou lze vyjmenovat pro prozkoumání všech původních výjimek, které byly vyvolány.