Minulý díl byl zaměřen především na novinky pro podporu fine-grained paralelizmu. Avšak někdy je třeba použít stará dobrá vlákna a ponořit se do vlastní multivláknové výzvy. Ani pro klasická vlákna nezůstává .NET Framework 4 pozadu.
reklama
Minulý díl byl zaměřen především na novinky pro podporu fine-grained paralelizmu. Avšak někdy je třeba použít stará dobrá vlákna a ponořit se do vlastní multivláknové výzvy. Ani pro klasická vlákna nezůstává .NET Framework 4 pozadu.
Jednou z pravděpodobně nejcitelněji chybějících věcí v .NET 2.0 byla chybějící podpora pro thread-safe generické kolekce. V .NETu 1.0/1.1 bylo možné využít metody Synchronized a obléci tak kolekci do thread-safe kabátu. Na výběr tedy zbyly dvě možnosti. Používat negenerické verze a Synchronized nebo si napsat kolekci vlastní (případně využít hotových knihoven).
.NET Framework 4 přináší nově mnoho thread-safe kolekcí. Nachází se ve jmenném prostoru Systém.Collections.Concurrent. Jedná se např. o ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T> a jiné. Výběr je opravdu bohatý. My se podíváme na použití ConcurrentQueue<T>.
ConcurrentQueue<int> fronta = new ConcurrentQueue<int>();
fronta.Enqueue(20);
int prvek;
if (fronta.TryDequeue(out prvek))
{
Console.WriteLine(prvek);
}
else
{
Console.WriteLine("Nic");
}
Výše je uveden jednoduchý příklad. Nejprve vytvoříme novou thread-safe frontu – konstruktor se neliší od konstruktoru běžné fronty. Stejně tak metoda Enqueue, která přidá prvek do fronty. Co vás však může překvapit je absence komplementární metody Dequeue. Skutečně není obsažena, neboť její použití v konkurenčním prostředí není v mnoha případech dobrý nápad. Většinou je třeba nejprve zjistit, zdali fronta vůbec nějaký prvek obsahuje a až poté jej získat. Ale mezi tímto zjištěním a vlastním získáním může jiné vlákno frontu pozměnit a výsledkem bude špatné chování. Proto existuje metoda TryDequeue (případně TryPop u zásobníku apod.), která vám vrátí požadovaný prvek a ještě informaci, jestli operace byla úspěšná.
Použití těchto datových struktur je velmi přímočaré, a pokud mezi vlákny kolekce využíváte, jejich záměna nebude jistě nijak obtížná.
Mimo kolekcí .NET Framework 4 přináší i nová synchronizační primitiva a podobné objekty, které mohou opravdu naši práci rapidně ulehčit.
Pokud je vaše kritická sekce v aplikaci velice krátká a čekání nebude dlouhé a víte (nebo tušíte), že přepnutí kontextu a přechody do režimu jádra by bylo náročnější než busy-waiting, pravděpodobně se poohlédnete po objektu ne nepodobnému objektu SpinWait resp. SpinLock. Tyto dva objekty jsou nové a slouží přesně k vyřešení situace, kterou jsem právě popsal. Jejich nasazení je však třeba velmi pečlivě vyzkoušet, neboť při nesprávném použití se aplikace může stát téměř nepoužitelnou.
S čekáním na jistou událost se nepřímo pojí i čekání na dokončení práce vláken (např. v ThreadPoolu). Jeden z nových objektů i CountdownEvent. Tento objekt dělá vlastně jen věc, kterou jste již minimálně jednou psali. Počítá, kolikrát někdo signalizoval událost a jakmile dojde k nule (od startovní hodnoty), vyvolá sám událost.
class Program
{
static void Main(string[] args)
{
CountdownEvent cde = new CountdownEvent(10);
for (int i = 0; i < cde.InitialCount; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Dummy), cde);
}
cde.Wait(2000);
Console.WriteLine("Za 2 sekundy hotovo: {0}.", cde.InitialCount - cde.CurrentCount);
cde.Wait();
Console.WriteLine("Vše hotovo.");
}
static void Dummy(object o)
{
Thread.Sleep(new Random().Next(5000));
(o as CountdownEvent).Signal();
}
}
Nakonec se ještě zmíním o několika menších „zlepšovácích“, které považuji za zajímavé.
Jedním z nich je Lazy<T> (a LazyInitializer) umožňující „líně“ inicializovat proměnnou. Objekt je to v zásadě jednoduchý, ale ve spojení s LazyExecutionMode vám může ušetřit psaní několika zbytečných řádků kódu. Podobně je možné využít i ThreadLocal<T> objekt, který můžete použít pro proměnné pro dané vlákno.
Posledním užitečným objektem je BlockingCollection<T>. Využití této kolekce je primárně pro klasickou synchronizační úlohu producent-konzument. Avšak není problém využít i ConcurrentQueue<T> nebo ConcurrentStack<T>. Tato kolekce pouze ulehčuje přímý scénář producent-konzument, který se velmi často při vícevláknovém zpracování v různých variacích objevuje.
Ačkoli jsme prošli jen malou část novinek ve vícevláknovém světě v .NET Frameworku 4, považuji jej za velmi podařené a užitečné. Pokud vás detaily zajímají více, doporučuji navštívit msdn.microsoft.com a zkoušet, zkoušet, zkoušet.