Transcript NET

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

wątków - szeregowanie w czasie operacji

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 LeaveCriticalSection

wejście do sekcji krytycznej wyjście z sekcji krytycznej

TryEnterCriticalSection

sprawdzenie sekcji krytycznej  .NET:

Monitor

Sekcja krytyczna

} lock (x) { ....

– C# - lock

} System.Threading.Monitor.Enter(x); try { finally { ...

} System.Threading.Monitor.Exit(x);  Kontekst pozwala identyfikować poszczególne instancje sekcji krytycznej  Aby skorzystać z konstrukcji typeof np. lock uzyskać globalny kontekst można (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  Mutex –

semafor binarny

  Semaphore –

semafo wielowartościowy

 Event –

zdarzenie

 Waitable timer –

budzik

: NT, 2K, XP  powiadomienie o zmianie w systemie plików Obiekty, które mają własności semaforów:  proces , wątek sygnalizowane po zakończeniu  zadanie - sygnalizowane po wyczerpaniu limitu czasu  plik sygnalizowane gdy nie trwają operacje we/wy  wejście konsoli buf.

sygnalizowane gdy są jakieś znaki w

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 

signaled – semafor podniesiony

not signaled – semafor opuszczony

 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

dwMiliseconds) ; (HANDLE hObject, DWORD 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

public SafeWaitHandle { get; set; }

SafeWaitHandle

{ get; set; }

Opuszczanie semafora .NET

class

ManualResetEvent

:

WaitHandle

; class

AutoResetEvent

:

WaitHandle

; class

Mutex

:

WaitHandle

; class

Semaphore

:

WaitHandle

;

  

Event

Zdarzenie

wydarzenia - obiekt dwustanowy, służący do sygnalizowania zajścia Synchronizacja wątków dowolnych procesów Typy zdarzeń  manual-reset event

-

podniesienie

wznawia wszystkie wątki

 automatycznie opuszczane -

jeden wątek podniesienie wznawia zawsze

  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

podniesienie, opuszczanie semafora:

WaitFor...

.NET:

Mutex

   

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

LONG lInitialCount , LONG (PSECURITY_ATRIBUTE

psa

, 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

wątki 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 Dennis Mac 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 F O R K Parallel Region J O I N 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, DWORD fdwFilter) ; // czy zmiana poddrzewa // rodzaj zmiany 

FindNextChangeNotification

- zlecenie dokonania kolejnego powiadomienia   

FindCloseChangeNotification

- rezygnacja

WaitFor

...

- oczekiwanie na powiadomienie .NET:

System.IO.FileSystemWatcher

 

Inne metody synchronizacji

WaitForInputIddle wMillisecinds) (HANDLE hProcess, DWORD .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

System.Timers.Timer

(inny wątek via ThreadPool)

(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 synchronizacja

- podniesienie wznawia wszystkie - podniesiony wznawia tylko jeden wątki wątek  operacje na budziku • •

CreateWaitableTimer

(P SECURITY_ATRIBUTE

bInitialOwned

, PCSTR

pszName

)

OpenWaitableTimer

psa

, BOOL • •

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 lock Get { if (_expensive == null) _expensive = new Expensive(); return _expensive; } } } ...

TLS

static ThreadLocal _x = new ThreadLocal (() => 3); var localRandom = new ThreadLocal ( () => 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ęć:  Obiekt 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)  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:  Odbieranie wartości z wątku  Odebranie i przekazanie ew. wyjątków  Czekanie na wątek (synchronizacja) blokuje inny wątek 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(); 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 Local Queue Program Thread Task 1 Task 2 Worker Thread 1 Task 3 Task 4 Task 5 Local Queue Worker Thread p

  

Task - .NET 4.X

Zeby zadanie cykli miało sens – jego długosc > 200-300 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 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 (()=> {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(); 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>(); 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.ContinueWhenAny(taskQueue.ToArray(), completedTask => { Console.WriteLine(completedTask.Result); } );

Task –przetwórz po kolei

} var taskQueue = new Queue>(); for (int i = 0; i < 10; i++) { taskQueue.Enqueue(Task.Factory.StartNew(() => { /*Do work.*/ })); // Perform some work with the tasks when they complete.

while (! taskQueue.IsEmpty) Task.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 • ConcurrentQueue • ConcurrentLinkedList • ConcurrentDictionary • ConcurrentBag • BlockingCollection • IProducerConsumerCollection • Partitioner, Partitioner, OrderablePartitioner • 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 szybko do 100 watkow … poza pulą – prowadzi    Optymalizacja: wystartowanie tylu dorzucanie nowych zadań ile jest rdzeni i w WaitAny 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 (()=> {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 { 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(); new Thread (() => { int ret = DoSmthg(); tcs.SetResult (ret); }) .Start(); Task 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(); 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

i Podejście wielowątkowe: Tworzymy kod jako synchroniczny 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 wielowątkowej synchronizacji złożonej logiki

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 _tcs = new TaskCompletionSource(); 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 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   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 odpowiedni typ i właściwość IsCompleted) zwracające 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 wykonujemy na nich await wołania na asynchroniczne i Z wyjątkiem głównych metod (obsługa zdarzeń w GUI) zamieniamy typy zwracane na Task lub Task 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();