NET

Report
Programowanie
Aplikacji Lokalnych
w Środowisku .NET
Synchronizacja
Obiekty synchronizacyjne
Pula wątków
MS Parallel Computing Initiative
Operacje asynchroniczne
Synchronizacja





Nieprzerywalne operacje
Ochrona sekcji krytycznej
Odmiany semaforów
Inne obiekty o własnościach semaforów
Inne metody synchronizacji
Synchronizacja

synchronizacja - szeregowanie w czasie operacji
wątków
Należy unikać!

aktywnego czekania jako metody synchronizacji
(SpinLock, SpinWait?)

Uśpienie wątku:
Thread.Sleep(TimeSpan)
Najprostszy sposób oczekiwania na koniec wątku Thread.Join(TimeSpan)

Synchronizacja w trybie użytkownika



Dużo mniej kosztowna niż wykorzystanie obiektów jądra
Możliwe jest synchronizowanie tylko wątków tego
samego procesu
Funkcje atomowe:
•
•
•
•
•
•

InterlockedIncrement,
InterlockedDecrement,
InterlockedExchancheAdd,
InterlockedExchanche, (32bit)
InterlockedExchanchePointer, (64bit)
InterlockedCompareExchanche
.NET - class Interlooked
•
CompareExchange, Decrement, Exchange, Increment
Sekcja krytyczna

Synchronizacja w trybie użytkownika
Możliwe jest synchronizowanie tylko wątków tego
samego procesu

Uwaga wykonanie „wait” może spowodować – zmianę trybu

Typ obiektu: CRITICAL_SECTION
Operacje na sekcji krytycznej:
•
•
•
•
•

InitializeCriticalSection inicjalizacja sekcji krytycznej
DeleteCriticalSection zwolnienie sekcji krytycznej
EnterCriticalSection wejście do sekcji krytycznej
LeaveCriticalSection wyjście z sekcji krytycznej
TryEnterCriticalSection sprawdzenie sekcji krytycznej
.NET: Monitor
Sekcja krytyczna – C# - lock
lock (x) {
....
}
System.Threading.Monitor.Enter(x);
try {
...
}
finally {
System.Threading.Monitor.Exit(x);
}
 Kontekst
pozwala identyfikować poszczególne
instancje sekcji krytycznej
 Aby uzyskać globalny kontekst można
skorzystać z konstrukcji typeof np.
lock(typeof(myObject))
synchronizowalne wrapery do zmiennych np.
Hashtable myHT = new Hashtable();
Hashtable mySyncHT = Hashtable.Synchronized(myHT);

Dostęp do zmiennych

Thread Local Storage
LocalDataStoreSlot lds =
Thread.GetNamedDataSlot("COOKIE");
Thread.SetData(lds, "abc");
var napis = (string) Thread.GetData(lds);
Synchronizowalne wrapery do zmiennych np.
Hashtable myHT = new Hashtable();
Hashtable mySyncHT = Hashtable.Synchronized(myHT);
lock(mySyncHT.SyncRoot) {
foreach (Object item in mySyncHT) { // to do smthg }

Obiekty synchronizacyjne
Synchronizacja w trybie jądra jest dużo bardziej kosztowna
 Odmiany semaforów
– semafor binarny
 Semaphore – semafo wielowartościowy
 Event – zdarzenie
 Waitable timer – budzik: NT, 2K, XP
 powiadomienie o zmianie w systemie plików
 Mutex

Obiekty, które mają własności semaforów:
wątek - sygnalizowane po zakończeniu
 zadanie - sygnalizowane po wyczerpaniu limitu czasu
 plik - sygnalizowane gdy nie trwają operacje we/wy
 wejście konsoli - sygnalizowane gdy są jakieś znaki w
buf.
 proces,
Nazwy obiektów synchronizacyjnych






korzystanie z jednego obiektu przez różne
procesy
małe i duże litery są rozróżniane
długość nieprzekraczająca _MAX_PATH
znaki dozwolone takie jak dla nazwy pliku (bez \)
obiekty synchronizacyjne dzielą tę samą
przestrzeń nazw
przestrzeń nazw dla obiektów
synchronizacyjnych jest rozłączna z przestrzenią
nazw systemu plików
Obiekt synchronizacyjny

obiekt przyjmujący stany
– semafor podniesiony
 not signaled – semafor opuszczony
 signaled

operacje
 zasygnalizowanie
(podniesienie semafora funkcje dedykowane dla typu obiektu
 likwidacja stanu zasygnalizowanego
(opuszczenie semafora) – funkcje wspólne
dla wszystkich typów obiektów
Opuszczanie semafora

opuszczenie pojedynczego semafora
DWORD WaitForSingleObject (HANDLE hObject, DWORD
dwMiliseconds) ;
INFINITE

opuszczanie zbioru semaforów
DWORD WaitForMultipleObjects (DWORD nCount, CONST HANDLE*
lpHandles, BOOL bWaitAll, DWORD dwMiliseconds)

Oczekiwanie przerywane komunikatami
MsgWaitForMultipleObjects

Oczekiwanie przerywane operacjami I/O
WaitForSingleObjectEx((HANDLE hObject, DWORD dwMiliseconds,
BOOL bAlertable) ;
WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx
SignalObjectAndWait(HANDLE hObjectToSignal,
HANDLE hObjectToWait, DWORD dwMiliseconds, BOOL
bAlertable) ;
Opuszczanie semafora .NET
class WaitHandle {
public virtual Boolean WaitOne();
public static Boolean WaitAll(WaitHandle[]);
public static Boolean WaitAny(WaitHandle[]);
…
public virtual Boolean WaitOne(int, bool);
public virtual Boolean WaitOne(TimeSpan, bool);
public virtual Boolean SignalAndWait (WaitHandle, WaitHandle)
public virtual Boolean SignalAndWait (WaitHandle, WaitHandle,
TimeSpan, Boolean);
public virtual IntPtr Handle { get; set; }
public SafeWaitHandle SafeWaitHandle { get; set; }
};
Opuszczanie semafora .NET
class ManualResetEvent : WaitHandle;
class AutoResetEvent : WaitHandle;
class Mutex : WaitHandle;
class Semaphore : WaitHandle;
Event




Zdarzenie - obiekt dwustanowy, służący do sygnalizowania zajścia
wydarzenia
Synchronizacja wątków dowolnych procesów
Typy zdarzeń
 manual-reset event - podniesienie wznawia wszystkie wątki
 automatycznie opuszczane - podniesienie wznawia zawsze
jeden wątek
Operacje na zdarzeniu
• CreateEvent (PSECURITY_ATRIBUTE psa,
BOOL bManual, BOOL bInitial, PCSTR pszName)
•
OpenEvent
•
SetEvent - podniesienie
•
ResetEvent - opuszczenie
•
PulseEvent - podniesienie i opuszczenie - zwalnia wszystkie
czekające wątki (manual) lub jeden(auto)

.Net: AutoResetEvent, ManualResetEvent
AutoResetEventSlim, ManualResetEventSlim (.Net 4.5)
Mutex






Semafor binarny - obiekt dwustanowy
Umożliwia synchronizację wątków dowolnych procesów
Podniesienie semafora binarnego wznawia tylko jeden wątek
Mutex jest własnością wątku:
 wątek nie czeka na zajętym już przez siebie Mutexie, ale trzeba
odpowiednią liczbę razy wołać ReleaseMutex)
 podnieść Mutexa może tylko wątek, który go opuścił (dla innych
wątków ReleaseMutex nie daje efektu)
 zakończenie wątku będącego włąścicielem muteksa podnosi ten
semafor -> wynik WaitFor... = WAIT_ABANDONED
Operacje na semaforze binarnym
• CreateMutex (PSECURITY_ATRIBUTE psa,
BOOL bInitialOwned, PCSTR pszName)
•
OpenMutex
•
ReleaseMutex
.NET:
Mutex
podniesienie, opuszczanie semafora: WaitFor...
Semaphore

Semafor wielowartościowy cechuje ograniczona liczba stanów (0
oznacza semafor opuszczony)
Próba opuszczenia semafor zmniejsza licznik o 1
Podniesienie semafora zwiększa licznik i wznawia tyle wątków, o ile
został zmniejszony licznik
Opuszczenie i podniesienie może być wykonane przez różne wątki

operacje na semaforze wielowartościowym



•
CreateSemaphore (PSECURITY_ATRIBUTE psa,
LONG lInitialCount, LONG lMaximumCount, PCSTR pszName)
•

OpenSemaphore
• ReleaseSemaphore podniesienie
.NET: Semaphore, SemaphoreSlim (>=.Net 4.5 – zalecany,
pracuje hybrydowo tj. nie uzywa obj. jądra póki nie jest to
konieczne)
.NET: ReaderWriterLock(Slim)

Semafor dedykowany dla asymetrycznej
sytuacji gdze istnieje możliwość wielu
dostępów vs. dostęp wyłączny

operacje na semaforze
wielowartościowym
•
•
•
•
•
•
•
IsReaderLockHeld, IsWriterLockHeld
WriterSeqNum
AcquireReaderLock, AcquireWriterLock
AnyWritersSince
UpgradeToWriterLock, DowngradeFromWriterLock
ReleaseReaderLock, ReleaseWriterLock
RestoreLock, ReleaseLock.
.NET: Barrier
Barrier jest obiektem synchronizacyjnym, który pozwala na
zatrzymanie wykonania większej liczby wątków w określonym
punkcie dopóki nie zostanie on osiagnięty przez wszystkie
wątki
Mac
Dennis
Charlie Gas Station = Barrier
Bost
on
Barrier - Code
.NET: CountdownEvent
CountdownEvent jest obiektem synchronizacyjnym który
pozwala śledzić wykonanie wiekszej liczby zadań i
sygnalizować ich zakończenie.
Master Thread
J
O
I
N
F
O
R
K
Parallel Region
Master Thread
CountdownEvent - Code
Powiadomienie o zmianie w systemie plików






powiadomienie jest semaforem, który zmienia stan na
podniesiony w chwili wystąpienia zmiany w systemie plików
zlecenie dokonania pierwszego powiadomienia
HANDLE FindFirstChangeNotification(
LPTSTR lpszPath,
// ścieżka
BOOL fWatchSubTree,
// czy zmiana poddrzewa
DWORD fdwFilter) ;
// rodzaj zmiany
FindNextChangeNotification - zlecenie dokonania
kolejnego powiadomienia
FindCloseChangeNotification - rezygnacja
WaitFor... - oczekiwanie na powiadomienie
.NET: System.IO.FileSystemWatcher
Inne metody synchronizacji


WaitForInputIddle (HANDLE hProcess, DWORD
wMillisecinds)
.NET : System.Diagnostics.Process.WaitForInputIdle
Czeka aż wątek główny przetworzy wszystkie
komunikaty (np. emulacja naciśnięć klawiszy)
O mierzeniu czasu

Sleep(x)

System.Threading.Timer (inny wątek via ThreadPool)
System.Timers.Timer (inny wątek)
Dedykowane dla okien:





System.Windows.Forms.Timer (WindowsForms)
System.Windows.Threading.DispatcherTimer (WPF)
Waitable timer
Waitable timer - NT/2K/XP




budzik jest obiektem czasu, który jest sygnalizowany po upływie
określonego czasu (jednorazowo lub cyklicznie)
typy budzików
 jednokrotne, cykliczne
sposób opuszczania
 ręczne opuszczane - podniesienie wznawia wszystkie wątki
 synchronizacja - podniesiony wznawia tylko jeden wątek
operacje na budziku
• CreateWaitableTimer(PSECURITY_ATRIBUTE psa, BOOL
bInitialOwned, PCSTR pszName)
• OpenWaitableTimer
• SetWaitableTimer (HANDLE hTimer,
const LARGE_INTEGER *pDueTime, LONG lPeriod,
PTIMERAPCROUTINE pfnCompletitionRoutine, PVOID
pvArgToCompletitionRoutine, BOOL fResume)
• CancelWaitableTimer - zatrzymanie
.NET vs API

Wołanie funkcji unmanage – wymaga ich zaimportowania:
[DllImport("msvcrt.dll")]
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
{
[DllImport("msvcrt.dll")]
public static extern int puts(string c);
[DllImport("msvcrt.dll")]
internal static extern int _flushall();
public static void Main()
{
puts("Test");
_flushall();
}
}
MSDN -> Platform Invoke Tutorial
System.Threading.Timer
Class Timer {
...
public Timer (TimerCallback);
public Timer (TimerCallback, Object, TimeSpan,
TimeSpan);
public Timer.Change (TimeSpan, TimeSpan);
...
};

F.callbacku jest obsługiwana przez thread pool
DeadLock
object l1 = new object();
object l2 = new object();
new Thread (() => {
lock (l1) {
Thread.Sleep (1000);
lock (l2); // Deadlock
}
}).Start();
lock (l2)
{
Thread.Sleep (1000);
lock (l1); // Deadlock
}
DeadLock - zapobieganie


Zajmowanie zasobów w takiej samej kolejności
Użycie WaitAll
Leniwa inicjalizacja
class Foo {
Expensive _expensive;
public Expensive Expensive { // Lazily instantiate Expensive
Get {
if (_expensive == null) _expensive = new Expensive();
lock
return _expensive;
}
}
...
}
TLS
static ThreadLocal<int> _x = new ThreadLocal<int> (() => 3);
var localRandom = new ThreadLocal<Random>
( () => new Random (Guid.NewGuid().GetHashCode()) );
TLS
class Test {
LocalDataStoreSlot _secSlot =
Thread.GetNamedDataSlot ("securityLevel");
int SecurityLevel
{
get {
object data = Thread.GetData (_secSlot);
return data == null ? 0 : (int) data; // null == unintl.
}
set { Thread.SetData (_secSlot, value); }
}
...
}
WIELOZADANIOWOŚĆ W .NET
Wątki
Kiedy używać:
Długie zadania, priorytet inny niż normal, foreground task
 new Thread (() => { } ).Start();
 new Thread (() => { } ).Start(startState);
Wynik dla:
for (int i = 0; i < 10; i++)
new Thread (() => Console.Write (i)).Start();
Koszt wątku:

Pamięć:
jądra (1.2 kB)
 Thread environment block (4/8 kB 32/64b)
 Stos (tryb użytkownika 1MB)
 Stos (tryb jądra 12/24kB dla 32/64b)
 Obiekt

DllMain -> DLL_THREAD_ATTACH/
DLL_THREAD_DETACH

Wniosek: wątki kosztują i nie należy ich
nadużywać
Wątki
Wątek CLR == wątek systemowy (jak dotąd)
Start nowego wątku:
 new
Thread (() => { } ).Start();
 new Thread (() => { } ).Start(startState);
Jaki będzie wynik dla:
for (int i = 0; i < 10; i++)
new Thread (() => Console.Write (i)).Start();
Wątki
Wątek CLR == wątek systemowy (jak dotąd)
Start nowego wątku:
 new
Thread (() => { } ).Start();
 new Thread (() => { } ).Start(startState);
Jaki będzie wynik dla:
for (int i = 0; i < 10; i++)
new Thread (() => Console.Write (i)).Start();
a dla
for (int i = 0; i < 10; i++) {
int temp = i;
new Thread (() => Console.Write (temp)).Start();
Wątki
Kiedy używać:
 długie
zadania,
 priorytet inny niż normal
 zadania foreground
Pula wątków - .NET


Obiekt systemowy: ThreadPool
Uruchamianie:
ThreadPool.QueueUserWorkItem (
notUsed => Console.WriteLine ("Msg from pool"));

Kiedy używać:
 krótkie
zadania (<250ms, idealnie <100ms),
 priorytet normal,
 zadania background

Używane przez : WCF, Remoting, ASP.NET, and
ASMX Web Services, System.Timers.Timer i
System.Threading.Timer, BackgroundWorker
asynchr. delegaty
Wątki, pula
Problemy:
wartości z wątku
 Odebranie i przekazanie ew. wyjątków
 Czekanie na wątek (synchronizacja) blokuje inny wątek
 Odbieranie
Czekanie:
new Thread (() => Console.Write (“test”)).Start().Join();
lub
var endOfWork = new CountdownEvent(10);
for (var i=1; i<=10; i++)
new Thread (() =>endOfWork.signal()).Start();
endOfWork.wait();
Czekanie inaczej
var threads = new list<Thread>();
for (var i=1; i<=10; i++) {
var t = new Thread (() =>endOfWork.signal());
t.Start();
threads.Add();
}
WaitHandle.WaitAny(threads, true); // false ?
Parallel Extensions .Net 4.x

.NET Library
 Mogą
być wykorzystywane we wszystkich
językach na platformie .NET

Obejmuje 3 różne elementy
 Parallel
LINQ (PLINQ)
 Task Parallel Library (TPL)
 Coordination Data Structures (CDS)
Od watków do zadań (1)
Od watków do zadań (2)
Task Scheduler
Global
Queue
Program
Thread
Task 1
Task 2
Local
Queue
Worker
Thread
1
Task 3
Task 4 Task 5
Local
Queue
Worker
Thread p
Task - .NET 4.X



Zeby zadanie miało sens – jego długosc > 200-300
cykli
Domyslnie scheduler używa globalnej kolejki
(można to zmienić)
Aby zapewnić np. priorytety lub niekorzystanie z
.Net puli wątków koniczne jest zaimplementowanie
własnego schedulera.
Task - .NET 4.X

Uruchamianie:
Task task = Task.Run (
() => Console.WriteLine (“Task")).Start();
Zadania są domyślnie realizowane przez pulę

Długie zadania (nie blokują puli):
Task task = Task.Factory.StartNew (
() => …, TaskCreationOptions.LongRunning);

Oczekiwanie na zakończenie zadania
task.Wait();
lub
Task<int> task = Task.Run (() => { return 3; });
Int ret = task.Result;
Task vs. wyjątki

Nieobsłużone wyjątki są przechowywane (i wyrzucane)

w momencie odczytu zwrotu
 lub wykonania Wait na zadaniu
 W 4.0 były wyrzucane przy finalizatorze (crash aplikacji)
CLR opakowuje wyjątki w AggregateException
Task task = Task.Run (
() => { throw new ApplicationException(“Problem”); });
try { task.Wait(); }
catch (AggregateException aex)
{
Console.WriteLine (aex.InnerException.ToString() );
}
 Stan zadania można sprawdzić przez IsFaulted i IsCanceled
 Sam wyjątek jest dostępny przez właściwość Exception

Task - kontynuacje
var basicTask=new Task<int> (()=> {return 5;} );
basicTask.ContinueWith (antecedent =>
{
int result = antecedent.Result;
Console.WriteLine (result); // Writes 123
});
basicTask.Start();


Zwykle kolejne zadania wykonuje ten sam wątek (dla gui
zawsze)
jesli chcemy mieć pewność: ContinueWith (antecedent =>{…},

TaskContinuationOptions.ExecuteSynchronously);
Można zdefiniować kilka następców. Domyślnie zostaną uruchomione
wszystkie na raz. ExecuteSynchronously je szerguje.

Inne opcje: NotOnCanceled, OnlyOnCanceled, LazyCancellation,
LongRunning, NotOnFaulted,
Task - zagnieżdzony
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
});
});
parent.Wait();
Task - potomny
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
}, TaskContinuationOptions. AttachedToParent);
});
parent.Wait();
Task zagnieżdzony vs potomny (-> AttachedToParent)


Rodzic nie czeka na zakończenie – dla p. czeka – tj nie konczy
wykonania przed dziecmi (ale nie robi join!!!)
Stan rodzica nie zależy od stanu t. zagnieżdżonego – dla p. zależy
Rodzic nie przechwytuje wyjątków – dla p. przechwytuje
Task – czekaj na wszystkie
var taskQueue = new Queue<Task>();
for (int i = 0; i < 10; i++) {
taskQueue.Enqueue(Task.Factory.StartNew(() => { /* Do work. */ }));
}
// Perform some work with the tasks when they complete.
Task.Factory.ContinueWhenAll(taskQueue.ToArray(),
completedTasks => { // Do continuation work.}
);
Task – czekaj na pierwszy
var taskQueue = new Queue<Task<int>>();
for (int i = 0; i < 10; i++) {
taskQueue.Enqueue(Task<int>.Factory.StartNew(() => { /*Do work.*/ }));
}
// Perform some work with the tasks when they complete.
Task.Factory.ContinueWhenAny(taskQueue.ToArray(),
completedTask => { Console.WriteLine(completedTask.Result); }
);
Task –przetwórz po kolei
var taskQueue = new Queue<Task<int>>();
for (int i = 0; i < 10; i++) {
taskQueue.Enqueue(Task<int>.Factory.StartNew(() => { /*Do work.*/ }));
}
// Perform some work with the tasks when they complete.
while (! taskQueue.IsEmpty)
Task<int>.Factory.ContinueWhenAny(taskQueue.ToArray(),
completedTask => {
Console.WriteLine(completedTask.Result);
taskQueue.Remove(completedTask);
}
);
Anulowanie przetwarzania
Tradycyjne podejście:
 zabicie watków –
Problemy:
 Wykonanie
akcji porzadkujących
 Jak zabic watki z puli?

Ustawienie flagi zakończ przetwarzanie
Problemy:
 Wątek
może czekać na obiekcie synchr.
Unified cancelation Model
CancellationTokenSource
Cancel()
OP1
OP2
OP3
UCM – przykład
System.Collections.Concurrent
•
Przy przetwarzaniu wielozadaniowym należy wykorzystywać
klasy kolekcji bezpiecznych ("thread-safe")
•ConcurrentStack<T>
•ConcurrentQueue<T>
•ConcurrentLinkedList<T>
•ConcurrentDictionary<TKey,TValue>
•ConcurrentBag<TKey,TValue>
•BlockingCollection<T>
•IProducerConsumerCollection<T>
•Partitioner, Partitioner<T>,
OrderablePartitioner<T>
•
zamiast
•System.Collections
•System.Collections.Generic
Parallel LINQ-to-Objects
(PLINQ)
• Wykorzystuje model "Task"
• Pozwala na wykorzystanie wielu rdzeni przy zapytaniach LINQ
• Wspiera w pełni standardowe operatory zapytań
• Minimalizuje wpływ na istniejące zapytania LINQ
var q = from p in people.AsParallel()
where p.Name == queryInfo.Name &&
p.State == queryInfo.State &&
p.Year >= yearStart &&
p.Year <= yearEnd
orderby p.Year ascending
select p;
Parallel Static Class
• Gdy instrukcje są niezależne moga być zrównoleglone
StatementA();
StatementB();
StatementC();
Parallel.Invoke(
() => StatementA(),
() => StatementB(),
() => StatementC() );
Parallel.For - Przykład
Paralel Invoke
public static void Invoke (params Action[] actions);
Parallel.Invoke (
() => new WebClient().DownloadFile
("http://www.wp.pl", “index.html"),
() => new WebClient().DownloadFile
("http://www.pg.gda.pl", “index.html")
);
Np. Cancelation token
public static void Invoke (ParallelOptions options,
params Action[] actions);
Paralel Enumerable
public static void Invoke (params Action[] actions);
val w = ParallelEnumerable.Range (1, 10000000).Sum (
i => Math.Sqrt (i))
public static void Invoke (ParallelOptions options,
params Action[] actions);
Granice zrównoleglania






Zadania mają sens jeśli są krótkie (nie za krótkie ).
100 dlugich zadan – prowadzi powoli do 100 watków w
puli a to jest nieefektywne
100 dlugich zadan z opcja longrunning – prowadzi
szybko do 100 watkow… poza pulą
Optymalizacja:
wystartowanie tylu zadań ile jest rdzeni i w WaitAny
dorzucanie nowych zadań.
Użycie Parallel loop/for/foreach - w opcjach można
określić ogranicznie paralelizmu:
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism =
System.Environment.ProcessorCount;
Parallel.For (sourceCollection, options, item => Process(item));
Task – kontynuacja po nowemu
var basicTask = new Task<int> (()=> {return 15;} );
var awaiter = basicTask.GetAwaiter();
awaiter.OnCompleted (() =>
{
int result = awaiter. GetResult();
Console.WriteLine (result); // Writes 123
});
basicTask.Start();
Task – completition source
public class TaskCompletionSource<TResult> {
public void SetResult (TResult result);
public void SetException (Exception exception);
public void SetCanceled();
public bool TrySetResult (TResult result);
public bool TrySetException (Exception exception);
public bool TrySetCanceled();
wołana jedna z
}
var tcs = new TaskCompletionSource<int>();
new Thread (() => { int ret = DoSmthg(); tcs.SetResult (ret); })
.Start();
Task<int> task = tcs.Task; // dedykowany, task oczekujacy
Console.WriteLine (task.Result); // poczeka i wypisze ret
completition source
Może być wykorzystane do czekania np. na I/O
Nie koniecznie musi czekać na rezultat z nowego wątku
tworzonego ex-plicite
Np.:
var timer = new System.Timers.Timer (1000)
{ AutoReset = false };
timer.Elapsed += delegate
{ timer.Dispose(); tcs.SetResult (10); };
completition source – DoAfter
Task DoAfter (int msToDelay)
{
var tcs = new TaskCompletionSource<object>();
var timer = new System.Timers.Timer (msToDelay)
{ AutoReset = false };
timer.Elapsed += delegate
{ timer.Dispose(); tcs.SetResult (null); };
timer.Start();
return tcs.Task;
}
DoAfter(1000).GetAwaiter()
.OnCompleted (() => DoSomethingAfter1s ());
Operacje asynchroniczne



Podejście wielowątkowe: Tworzymy kod jako synchroniczny
i wywołujemy go w oddzielnym wątku
Podejście asynchroniczne: Funkcja może działać jeszcze po
zwróceniu sterowania. Dopóki nie próbujemy uzyskać
wyniku od operacji działającej istotnie długo – nie ma
wpływu na wątek który ja wywołał (model zalecany np. dla
metro, SL)
Przykładem są np. ContinueWith/OnContinue
2 typowe scenariusze:
 Po stronie serwerowej duza ilość operacji IO
 W aplikacji klienckiej uproszenie złożonej logiki
wielowątkowej synchronizacji
Operacje asynchroniczne





Model zalecany dla np. dla metro, SL
Nie startujemy nowych wątków ex-plicite
długie operacje (>=50ms) uruchamiamy jako
asynchroniczne
Krótkie operacje robimy w wątku GUI
Zasadniczo można nie robić synchroniazacji
Przykład tradycyjny
int GetPrimesCount (int start, int count) {
return ParallelEnumerable.Range (start, count).Count (
n => Enumerable.Range (2, (int)Math.Sqrt(n)-1).All (
i => n % i > 0));
}
void DisplayPrimeCounts() {
const int przedzial = 1000000;
for (int i = 0; i < 10; i++)
Console.WriteLine (" Znaleziono: " +
GetPrimesCount (i*przedzial + 2, przedzial) +
" liczb pierwszych między " + (i* przedzial) +
" oraz " + ((i+1)* przedzial -1));
}
Przykład wielowątkowy
int GetPrimesCountAsync (int start, int count) {
return Task.Run (
() => ParallelEnumerable.Range (start, count).Count (
n =>Enumerable.Range (2, (int) Math.Sqrt(n)-1).All (
i => n % i > 0)));
}
void DisplayPrimeCounts() {
const int przedzial = 1000000;
for (int i = 0; i < 10; i++) {// kolejnosc ???
var awaiter = GetPrimesCountAsync (i*1000000 + 2,
1000000).GetAwaiter();
awaiter.OnCompleted (() => Console.WriteLine (
“Znaleziono:” + awaiter.GetResult() + “... "));
}
}
Przykład asynchroniczny
void DisplayPrimeCountsFrom (int i, int count ){
var awaiter = GetPrimesCountAsync (i*1000000 + 2,
1000000).GetAwaiter();
awaiter.OnCompleted (() => {
Console.WriteLine (“Znaleziono:”, awaiter.GetResult()+ "…");
if (count>0) DisplayPrimeCountsFrom (i,count-1);
else Console.WriteLine ("Done");
});
}
void DisplayPrimeCounts() {
DisplayPrimeCountsFrom (0, 10);
}
 DisplayPrimeCounts sam nie jest asynchroniczna
Przykład asynchroniczny II
class PrimesStateMachine {
const int MaxCount = 10;
TaskCompletionSource<object> _tcs = new
TaskCompletionSource<object>();
public Task Task { get { return _tcs.Task; } }
public void DisplayPrimeCountsFrom (int i) {
var awaiter = GetPrimesCountAsync (i*1000000+2,
1000000).GetAwaiter();
awaiter.OnCompleted (() => {
Console.WriteLine (awaiter.GetResult());
if (i++ < MaxCount) DisplayPrimeCountsFrom (i);
else { Console.WriteLine ("Done"); _tcs.SetResult (null); }
});
}
}
Przykład asynchroniczny II cd.
Task DisplayPrimeCountsAsync() {
var machine = new PrimesStateMachine();
machine.DisplayPrimeCountsFrom(0);
return machine.Task;
}
Przykład asynchroniczny 5.0
Task<int> GetPrimesCountAsync (int start, int count) {
return Task.Run (
() => ParallelEnumerable.Range (start, count).Count (
n =>Enumerable.Range (2, (int)Math.Sqrt(n)-1).All (
i => n % i > 0)));
}
async void DisplayPrimeCounts()
{
for (int i = 0; i < 10; i++)
Console.WriteLine (
await GetPrimesCountAsync (i*1000000+2, 1000000));
}
Przykład asynchroniczny 5.0
async void DisplayPrimesCount()
{
int result = await GetPrimesCountAsync (2, 1000000);
Console.WriteLine (result);
}
Odpowiada nast kodowi
void DisplayPrimesCount() {
var awaiter = GetPrimesCountAsync (2, 1000000).GetAwaiter();
awaiter.OnCompleted (() =>
{
int result = awaiter.GetResult();
Console.WriteLine (result);
});
}
async

Może być zaaplikowany do metod zwracających



void
Task
Task<T>

Nie zmienia sygnatury (podobnie jak unsafe)
W ciele metody napotkanie await zwraca sterownie
(podobnie jak yield)

Async może być dodany do labdy i metod anonimowych

await


Typowo wywoływany jest na Task-u
Wystarczy aby przedmiot wołania await miał



Metodę GetAwaiter która zwróci obiekt implementujący
INotifyCompletion.OnCompleted (tj. GetResult zwracające
odpowiedni typ i właściwość IsCompleted)
Może wystąpic w metodach asynchronicznych praktycznie
wszędzie z wyjątkiem:




catch / finally
Wyrażenia lock,
Kontekstu unsafe
Punktu wejścia do aplikacji (main method).
Konstrukcja bogatego GUI



Piszemy metody synchronicznie
Zamieniamy synchroniczne wołania na asynchroniczne i
wykonujemy na nich await
Z wyjątkiem głównych metod (obsługa zdarzeń w GUI)
zamieniamy typy zwracane na Task lub Task<TResult> tak
by można było na nich czekać
Równoległość ponownie
Wołanie metod async bez await powoduje ich równoległe
wykonanie.
async Task DoSmthg1()
{
…
}
async Task DoSmthg2()
{
…
}
var task1 = DoSmthg1();
var task2 = DoSmthg2();

similar documents