101
Programowanie równoległe Część 4 – własne wnioski Jakub Binkowski

Programowanie równoległe Część 4 – własne wnioski

  • Upload
    willow

  • View
    30

  • Download
    0

Embed Size (px)

DESCRIPTION

Programowanie równoległe Część 4 – własne wnioski. Jakub Binkowski. O mnie. Jakub Binkowski Senior .NET Developer @ Rule Financial Microsoft Most Valuable Professional (MVP) MCP, MCTS, MCPD Lider Łódzkiej Grupy Profesjonalistów IT & .NET [email protected]. - PowerPoint PPT Presentation

Citation preview

Page 1: Programowanie równoległe Część 4 – własne wnioski

Programowanie równoległeCzęść 4 – własne wnioski

Jakub Binkowski

Page 2: Programowanie równoległe Część 4 – własne wnioski

O mnie Jakub Binkowski Senior .NET Developer @ Rule Financial

Microsoft Most Valuable Professional (MVP) MCP, MCTS, MCPD

Lider Łódzkiej Grupy Profesjonalistów IT & .NET

[email protected]

Page 3: Programowanie równoległe Część 4 – własne wnioski

Cykl „Programowanie równoległe w .NET” Część I

Wielowątkowość w .NET 4.0 Część II

Interakcja (synchronizacja) między wątkami, struktury danych i algorytmy równoległe

Część IIIOperacje asynchroniczne

Część IVDobre praktyki, najczęstsze błędy, testowanie

Page 4: Programowanie równoległe Część 4 – własne wnioski

Agenda Błędy Dobre praktyki Ogólne rady Testowanie

Page 5: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia

Page 6: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia

var list = new List<int> {1, 3, 4, 5};

Co zwróci instrukcja: list.Contains(5);

wywołana jednocześnie z: list.Insert(index: 1, item: 2);

?

Page 7: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Czym jest List<T>?

length = 4

items = 1 3 4 50 1 2 3 4

Page 8: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

Page 9: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

FALSE

Page 10: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

FALSE

Page 11: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

FALSE

Page 12: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

TRUE

Page 13: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 50 1 2 3 4

Page 14: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 50 1 2 3 4

Page 15: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 5 50 1 2 3 4

Page 16: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 5 50 1 2 3 4

Page 17: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 4 50 1 2 3 4

Page 18: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 4 50 1 2 3 4

Page 19: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 3 4 50 1 2 3 4

Page 20: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 3 4 50 1 2 3 4

2

Page 21: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 2 3 4 50 1 2 3 4

2

Page 22: Programowanie równoległe Część 4 – własne wnioski

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 5

items = 1 2 3 4 50 1 2 3 4

Page 23: Programowanie równoległe Część 4 – własne wnioski

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 4

items = 1 3 4 5 50 1 2 3 4

5? FALSE

Page 24: Programowanie równoległe Część 4 – własne wnioski

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 4

items = 1 3 4 4 50 1 2 3 4

5? FALSE

Page 25: Programowanie równoległe Część 4 – własne wnioski

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 4

items = 1 3 3 4 50 1 2 3 4

5? FALSE

Page 26: Programowanie równoległe Część 4 – własne wnioski

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 4

items = 1 2 3 4 50 1 2 3 4

5? FALSE

2

Page 27: Programowanie równoległe Część 4 – własne wnioski

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 5

items = 1 2 3 4 50 1 2 3 4

Page 28: Programowanie równoległe Część 4 – własne wnioski

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 5

items =

List.Contains(5) = false!

1 2 3 4 50 1 2 3 4

Page 29: Programowanie równoległe Część 4 – własne wnioski

Dobra praktyka Nie zakładaj, że jakieś 2 operacje są

bezpieczne wielowątkowo

Synchronizuj jednoczesny dostęp: lock (Monitor) ReaderWriterLock(Slim) itp.

Używaj kolekcji bezpiecznych wielowątkowo:System.Collections.Concurrent

Page 30: Programowanie równoległe Część 4 – własne wnioski

Instrukcje (nie)atomowe

Page 31: Programowanie równoległe Część 4 – własne wnioski

Problem: Instrukcje (nie)atomowe

jedna linia !=

jedna instukcja

Page 32: Programowanie równoległe Część 4 – własne wnioski

Co się stało? i++ to 3 instrukcje:

LOAD @i, R0 INCREMENT R0 STORE R0, @i

Page 33: Programowanie równoległe Część 4 – własne wnioski

Co się stało?Wątek 1

R0 = 0

i++

Wątek 2

R0 = 0

i++

Pamięć:

i = 5

Page 34: Programowanie równoległe Część 4 – własne wnioski

Co się stało?Wątek 1

R0 = 0

i++

Wątek 2

R0 = 0

i++

Pamięć:

i = 5

Page 35: Programowanie równoległe Część 4 – własne wnioski

Co się stało?Wątek 1

R0 = 0

i++: LOAD @i, R0

Wątek 2

R0 = 0

i++:

LOAD @i, R0

Pamięć:

i = 5

Page 36: Programowanie równoległe Część 4 – własne wnioski

Co się stało?Wątek 1

R0 = 5

i++: LOAD @i, R0

Wątek 2

R0 = 5

i++:

LOAD @i, R0

Pamięć:

i = 5

Page 37: Programowanie równoległe Część 4 – własne wnioski

Co się stało?Wątek 1

R0 = 6

i++: LOAD @i, R0 INC R0

Wątek 2

R0 = 5

i++:

LOAD @i, R0

Pamięć:

i = 5

Page 38: Programowanie równoległe Część 4 – własne wnioski

Co się stało?Wątek 1

R0 = 6

i++: LOAD @i, R0 INC R0 STORE R0, @i

Wątek 2

R0 = 5

i++:

LOAD @i, R0

Pamięć:

i = 6

Page 39: Programowanie równoległe Część 4 – własne wnioski

Co się stało?Wątek 1

R0 = 6

i++: LOAD @i, R0 INC R0 STORE R0, @i

Wątek 2

R0 = 6

i++:

LOAD @i, R0 INC R0

Pamięć:

i = 6

Page 40: Programowanie równoległe Część 4 – własne wnioski

Co się stało?Wątek 1

R0 = 6

i++: LOAD @i, R0 INC R0 STORE R0, @i

Wątek 2

R0 = 6

i++:

LOAD @i, R0 INC R0 STORE R0, @i

Pamięć:

i = 6

Page 41: Programowanie równoległe Część 4 – własne wnioski

Dobra praktyka Nie zakładaj, że „jedna linia = jedna

instrukcja”

Synchronizuj dostęp (lock, itp.)

Używaj gwarantowanych operacji atomowych, np.:Interlocked.Increment(ref _counter);

Page 42: Programowanie równoległe Część 4 – własne wnioski

Zdarzenia (events)

Page 43: Programowanie równoległe Część 4 – własne wnioski

Zdarzenia (events) - przypomnienie Deklaracja:public event EventHandler SomethingHappened;

Wywołanie:if (SomethingHappened != null)

SomethingHappened(this, EventArgs.Empty);

Subskrypcja:SomethingHappened += HandleSomethingHappened;

SomethingHappened -= HandleSomethingHappened;

Page 44: Programowanie równoległe Część 4 – własne wnioski

Problem: Zdarzenia a wielowątkowość

Czy zdarzenia są bezpiecznewielowątkowo?

Page 45: Programowanie równoległe Część 4 – własne wnioski

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened = null

Page 46: Programowanie równoległe Część 4 – własne wnioski

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened = HandleSomethingHappen

ed

Page 47: Programowanie równoległe Część 4 – własne wnioski

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened = HandleSomethingHappen

ed

Page 48: Programowanie równoległe Część 4 – własne wnioski

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened = HandleSomethingHappen

ed

true

Page 49: Programowanie równoległe Część 4 – własne wnioski

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened -=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened = HandleSomethingHappen

ed

Page 50: Programowanie równoległe Część 4 – własne wnioski

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened -=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened = null

Page 51: Programowanie równoległe Część 4 – własne wnioski

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened -=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened(this,

EventArgs.Empty);

SomethingHappened = null

Page 52: Programowanie równoległe Część 4 – własne wnioski

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened -=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened(this,

EventArgs.Empty);

SomethingHappened = null

NullReferenceException

Page 53: Programowanie równoległe Część 4 – własne wnioski

Przykład

Zdarzenia bezpieczne

wielowątkowo

Page 54: Programowanie równoległe Część 4 – własne wnioski

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Page 55: Programowanie równoległe Część 4 – własne wnioski

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Page 56: Programowanie równoległe Część 4 – własne wnioski

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Page 57: Programowanie równoległe Część 4 – własne wnioski

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Page 58: Programowanie równoległe Część 4 – własne wnioski

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Snippet„invok

e”

Page 59: Programowanie równoległe Część 4 – własne wnioski

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Page 60: Programowanie równoległe Część 4 – własne wnioski

Zdarzenia – pytanie Czy subskrypcja jest bezpieczna

wielowątkowo?SomethingHappened += HandleSomethingHappened;

A co jeśli dodajemy drugą subskrypcję? Czy łączenie dwóch delegatów jest bezpieczne?

SomethingHappened += HandleSomethingHappened;SomethingHappened += HandleSomethingHappened2;

Page 61: Programowanie równoległe Część 4 – własne wnioski

Events internals – C# 1.0-3.5private EventHandler _somethingHappened;

public event EventHandler SomethingHappened

{

add

{

lock (this)

{

_somethingHappened = somethingHappened + value;

}

}

remove {/*kod analogiczny*/}

}

Page 62: Programowanie równoległe Część 4 – własne wnioski

Events internals – C# 4.0private EventHandler _somethingHappened;

public event EventHandler SomethingHappened

{

add

{

/*bezpieczny wielowątkowo kod wolny od lock, który dodaje delegat to _somethingHappened*/

}

remove {/*kod analogiczny*/}

}

Page 63: Programowanie równoległe Część 4 – własne wnioski

Zdarzenia – odpowiedzi Czy subskrypcja jest bezpieczna

wielowątkowo? Tak!

Generowany kod jest wielowątkowo bezpieczny.

Czy łączenie dwóch delegatów jest bezpieczne?

Tak! Złączenie 2 delegatów powoduje powstanie trzeciego. Delegaty są stałe, a więc z definicji wielowątkowo bezpieczne.

Page 64: Programowanie równoległe Część 4 – własne wnioski

Lock escape

Page 65: Programowanie równoległe Część 4 – własne wnioski

Przykład

Ucieczkaz lock’a

Page 66: Programowanie równoległe Część 4 – własne wnioski

Problempublic class SafeList<T>: IEnumerable<T>

{

private List<T> _list = new List<T>();

/*...*/

public IEnumerator<T> GetEnumerator()

{

lock (_list)

{

return _list.GetEnumerator();

}

}

}

Page 67: Programowanie równoległe Część 4 – własne wnioski

Problempublic class SafeList<T>: IEnumerable<T>

{

private List<T> _list = new List<T>();

/*...*/

public IEnumerator<T> GetEnumerator()

{

lock (_list)

{

return _list.GetEnumerator();

}

}

}

Zwrócony enumerator wcale nie jest zabezpieczony!

Page 68: Programowanie równoległe Część 4 – własne wnioski

Dobre praktyki Unikaj zwracania enumeratora kolekcji

„chronionych” przez lock („lock escape”)

GetEnumerator() może: iterować po kopii kolekcji zwracać wielowątkowo bezpieczny iterator

Page 69: Programowanie równoległe Część 4 – własne wnioski

Jak to wygląda w .NET? Enumerator po migawce (snapshot) kolekcji:

ConcurrentQueue ConcurrentStack ConcurrentBag

Enumerator dynamiczny: ConcurrentDictionary

Page 70: Programowanie równoległe Część 4 – własne wnioski

Przykład Poprawna implementacja GetEnumerable():

enumeracja po kopii wielowątkowo bezpieczny enumerator

Page 71: Programowanie równoległe Część 4 – własne wnioski

Lock leak

Page 72: Programowanie równoległe Część 4 – własne wnioski

Przykład

Wyciek lock’a

Page 73: Programowanie równoległe Część 4 – własne wnioski

Lock leak – zły kodprivate object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); } }}

Page 74: Programowanie równoległe Część 4 – własne wnioski

Lock leak – zły kodprivate object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); } }}

Wykonujemy nieznany zewnętrzny kod wewnątrz „lock”

Page 75: Programowanie równoległe Część 4 – własne wnioski

Przykład

Poprawiony kod

Page 76: Programowanie równoległe Część 4 – własne wnioski

private object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... }

var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); }}

Lock leak – poprawiony kod

Page 77: Programowanie równoległe Część 4 – własne wnioski

private object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... }

var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); }}

Lock leak – poprawiony kod

Page 78: Programowanie równoległe Część 4 – własne wnioski

private object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... }

var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); }}

Lock leak – poprawiony kod

Wykonujemy nieznany zewnętrzny kod poza „lock”

Page 79: Programowanie równoległe Część 4 – własne wnioski

Kod wielowątkowo bezpieczny

Dobre rady wujka Kuby

Page 80: Programowanie równoległe Część 4 – własne wnioski

Kilka ważnych pytań Czy wiesz gdzie w Twojej aplikacji wątki się

rodzą, a gdzie (i jak) umierają?

Czy interakcja między wątkami odbywa się w ściśle określonych miejscach?

Czy dokumentujesz założenia dot. wielowątkowości kodu?

Czy Twój kod jest prosty?

Page 81: Programowanie równoległe Część 4 – własne wnioski

Jak wygląda start wątku w Twojej aplikacji?

Page 82: Programowanie równoległe Część 4 – własne wnioski

Wątek to nie bomba! Dobrze przemyśl, w którym miejscu

wywoływany jest kod asynchroniczny: interakcje użytkownika (wątek UI) Timer, Task / ThreadPool komunikacja (WCF, messaging, itp.)

Page 83: Programowanie równoległe Część 4 – własne wnioski

Wątek to nie bomba! Zastanów się, co dzieje się z wątkiem (kodem)

później.

Czy łapiesz na końcu (początku) ew. wyjątki?

Czy przy wyjściu z aplikacji czekasz na zakończenie utworzonych wątków?

Podejście „let the shit hit the fan” się nie sprawdza!

Page 84: Programowanie równoległe Część 4 – własne wnioski

Interakcje między wątkami Zastanów się, w którym miejscu się wątki

spotykają.

Czy te klasy i metody są odpowiednio zabezpieczone?

Czy te interakcje są przejrzyste?

Page 85: Programowanie równoległe Część 4 – własne wnioski

Dokumentowanie Czy taki wpis jest Ci obcy?/// <remarks>

/// Any public static (Shared in Visual Basic)

/// members of this type are thread safe.

/// Any instance members are not guaranteed

/// to be thread safe.

/// </remarks>

Albo taki?/// <remarks>

/// This method is thread safe.

/// </remarks>

Page 86: Programowanie równoległe Część 4 – własne wnioski

Co warto dokumentować? Czy klasa została zaprojektowana dla

wielowątkowego użycia? Założenia – czego wymaga i co gwarantuje

dany kod Klasy aktywne (wywołujące kod

asynchroniczny):/// <remarks>/// Event is invoked on ThreadPool thread./// </remarks>public event EventHandler MessageReceived;

Bardziej skomplikowane algorytmy Cykl życia klasy a wielowątkowość

Page 87: Programowanie równoległe Część 4 – własne wnioski

Złoty środek Komentarze sprawiają, że przejrzysty kod

jest bardziej zrozumiały

Jeżeli na 1 linię kodu przypada 5 wyjaśnień, to może lepiej napisać inaczej kod?

Page 88: Programowanie równoległe Część 4 – własne wnioski

Prostota Jedynym sposobem oceny poprawności kodu –

analiza

Wielowątkowość sama z siebie jest skomplikowana i trudna. Niech kod wielowątkowy będzie chociaż prosty. Inaczej nikt nie będzie w stanie ocenić jego poprawności!

Brzydki kod + nieprzemyślana wielowątkowość = spaghetti2

Page 89: Programowanie równoległe Część 4 – własne wnioski

Wielowątkowość a testowalność

Page 90: Programowanie równoległe Część 4 – własne wnioski

Testowanie a kod równoległy Test jednostkowy:

Przewidywalne zachowanie Każde uruchomienie daje taki sam rezultat (sukces

lub porażka)

Wykonanie kodu na wielu wątkach: przypadkowe za każdym razem inne

Trudno jest napisać testy do kodu wielowątkowego

Page 91: Programowanie równoległe Część 4 – własne wnioski

CHESS – projekt Microsoft Research Próba okiełznania przypadkowości Zarządzanie przeplotem między wątkami Uruchomienie testu na różnych kombinacjach

przeplotu Możliwość odtworzenia przeplotu!

Tylko Visual Studio 2008 Prawdopodobnie jako część Visual Studio 11

Page 92: Programowanie równoległe Część 4 – własne wnioski

Jak pisać testy jednostkowe? Kod „jednowątkowy” można łatwo testować

Kod wielowątkowy testować jest trudno

A gdyby tak pozbyć się równoległości?

Page 93: Programowanie równoległe Część 4 – własne wnioski

Przykład

Testowanie koduwielowątkowego

Page 94: Programowanie równoległe Część 4 – własne wnioski

Testowanie – czego można się „pozbyć”? Konstrukcje wielowątkowe:

ThreadPool.QueueUserWorkItem Thread.Start Task (TaskScheduler) Timer

Wzorce: nieskończona pętla warunek

Page 95: Programowanie równoległe Część 4 – własne wnioski

Podsumowanie

Page 96: Programowanie równoległe Część 4 – własne wnioski

Podsumowanie Uwaga na częste błędy! Warto przemyśleć wielowątkowość w aplikacji Kod prosty, przejrzysty i udokumentowany Nie da się przetestować bezpieczeństwa

wielowątkowego Da się testować kod wielowątkowy – wystarczy

pozbyć się wielowątkowości!

Page 97: Programowanie równoległe Część 4 – własne wnioski

Błędy - disclaimer

Nie róbcie tego w domu!Ani w pracy!

Zawodowcy popełnili za Was te błędy,

żebyście Wy nie musieli.

Page 100: Programowanie równoległe Część 4 – własne wnioski

Przydatne odnośniki Events:

http://blogs.msdn.com/b/cburrows/archive/2010/03/05/events-get-a-little-overhaul-in-c-4-part-i-locks.aspx

http://blogs.msdn.com/b/cburrows/archive/2010/03/08/events-get-a-little-overhaul-in-c-4-part-ii-semantic-changes-and.aspx

http://blogs.msdn.com/b/cburrows/archive/2010/03/18/events-get-a-little-overhaul-in-c-4-part-iii-breaking-changes.aspx

http://blogs.msdn.com/b/cburrows/archive/2010/03/30/events-get-a-little-overhaul-in-c-4-afterward-effective-events.aspx

Page 101: Programowanie równoległe Część 4 – własne wnioski

Dziękuję za uwagę. Pytania?