Wskazówki: tworzenie prostego składnika wielowątkowego za pomocą języka C#
BackgroundWorker Składnika zastępuje i dodaje funkcje do System.Threading nazw; Jednakże System.Threading obszaru nazw jest zachowywana zarówno dla zgodności z poprzednimi wersjami i wykorzystania w przyszłości, jeśli wybierzesz.Aby uzyskać więcej informacji, zobacz BackgroundWorker — Informacje o składniku.
Można napisać aplikacje, które są zdolne do wykonywania wielu zadań jednocześnie.Ta możliwość o nazwie wielowątkowości, lub wolnych wątków, jest wygodnym sposobem projektowania składników, które procesor i wymagają danych wejściowych użytkownika.Wykorzystanie przykład składnika, który może spowodować, że wielowątkowość byłoby składnik, który oblicza informacje listy płac.Składnik może przetwarzać dane wprowadzone do bazy danych przez użytkownika na jeden wątek podczas obliczania listy płac procesor były wykonywane w innym.Uruchamiając te procesy osobne wątki, użytkownicy nie muszą czekać na komputerze do wykonania obliczeń przed wprowadzeniem dodatkowych danych.W tym instruktażu utworzy prosty składnik wielowątkowe, który wykonuje jednocześnie kilka złożonych obliczeń.
Tworzenie projektu
Aplikacja składa się z jednego formularza i składnika.Użytkownika będą wprowadzane wartości i sygnał rozpoczęcia obliczeń do składnika.Formularz będzie następnie otrzymywać wartości składnika i wyświetlić je w formantów etykiet.Składnik będzie wykonywać obliczenia procesor i sygnału formularza po zakończeniu.Zmienne publiczne utworzy w składniku do przechowywania wartości otrzymanych od interfejsu użytkownika.Będzie także implementować metody w składniku wykonywać obliczenia na podstawie wartości tych zmiennych.
[!UWAGA]
Mimo że funkcja jest zwykle korzystniejsze dla metody, która oblicza wartość argumentów nie mogą być przekazywane pomiędzy wątkami, ani mogą być zwracane wartości.Wiele sposobów proste do dostarczania wartości wątków oraz otrzymują od ich wartości.W tej demonstracji poprzez aktualizację zmiennych publicznych zwróci wartości do interfejsu użytkownika i zdarzenia będą używane do powiadamiania głównego programu po zakończeniu wykonywania wątku.
Okna dialogowe i polecenia menu może być różnią się od opisane w pomocy w zależności od ustawień aktywny lub edition.Aby zmienić ustawienia, wybierz polecenie Ustawienia eksportu i importu na Narzędzia menu.Aby uzyskać więcej informacji, zobacz Dostosowywanie ustawień środowiska deweloperskiego w Visual Studio.
Aby utworzyć formularz
Utwórz nowy Aplikacji Windows projektu.
Nazwa aplikacji Calculations i zmień nazwę Form1.cs jako frmCalculations.cs.Gdy Visual Studio monituje, aby zmienić nazwę Form1 kod elementu, kliknij przycisk Tak.
Ten formularz będzie służyć jako podstawowy interfejs aplikacji.
Dodać pięć Label kontroluje cztery Button formanty i jednego TextBox formantu do formularza.
Ustaw właściwości tych formantów w następujący sposób:
Formant
Nazwa
Tekst
label1
lblFactorial1
(puste)
label2
lblFactorial2
(puste)
label3
lblAddTwo
(puste)
label4
lblRunLoops
(puste)
label5
lblTotalCalculations
(puste)
button1
btnFactorial1
Silnia
button2
btnFactorial2
Silnia - 1
button3
btnAddTwo
Dodać dwie
button4
btnRunLoops
Uruchom pętli
textBox1
txtValue
(puste)
Aby utworzyć składnik kalkulatora
Z projektu menu wybierz Dodać składnik.
Nazwa składnika Calculator.
Aby dodać zmiennych publicznych do składnika kalkulatora
Otwórz Edytor kodu dla Calculator.
Dodać instrukcje do tworzenia zmiennych publicznych, które będą używane do przekazania wartości z frmCalculations do każdego wątku.
Zmienna varTotalCalculations będzie Zachowaj sumy całkowitej liczby obliczenia wykonywane przez składnik i inne zmienne otrzymają wartości z formularza.
public int varAddTwo; public int varFact1; public int varFact2; public int varLoopValue; public double varTotalCalculations = 0;
Aby dodać metod i zdarzeń do składnika kalkulatora
Zadeklarować delegatów zdarzenia, które składnik będą używać do komunikowania się wartości do formularza.
[!UWAGA]
Chociaż czterech zdarzeń będzie deklarowania, trzeba tylko tworzenie trzech delegatów, ponieważ dwa zdarzenia będą miały taki sam podpis.
Bezpośrednio pod deklaracjami zmiennych w poprzednim kroku, wpisz następujący kod:
// This delegate will be invoked with two of your events. public delegate void FactorialCompleteHandler(double Factorial, double TotalCalculations); public delegate void AddTwoCompleteHandler(int Result, double TotalCalculations); public delegate void LoopCompleteHandler(double TotalCalculations, int Counter);
Zadeklarować zdarzenia, które składnik będą używać do komunikowania się z aplikacji.W tym przez dodanie następującego kodu tuż poniżej kod wprowadzony w poprzednim kroku.
public event FactorialCompleteHandler FactorialComplete; public event FactorialCompleteHandler FactorialMinusOneComplete; public event AddTwoCompleteHandler AddTwoComplete; public event LoopCompleteHandler LoopComplete;
Bezpośrednio pod kod wpisanej w poprzednim kroku, wpisz następujący kod:
// This method will calculate the value of a number minus 1 factorial // (varFact2-1!). public void FactorialMinusOne() { double varTotalAsOfNow = 0; double varResult = 1; // Performs a factorial calculation on varFact2 - 1. for (int varX = 1; varX <= varFact2 - 1; varX++) { varResult *= varX; // Increments varTotalCalculations and keeps track of the current // total as of this instant. varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; } // Signals that the method has completed, and communicates the // result and a value of total calculations performed up to this // point. FactorialMinusOneComplete(varResult, varTotalAsOfNow); } // This method will calculate the value of a number factorial. // (varFact1!) public void Factorial() { double varResult = 1; double varTotalAsOfNow = 0; for (int varX = 1; varX <= varFact1; varX++) { varResult *= varX; varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; } FactorialComplete(varResult, varTotalAsOfNow); } // This method will add two to a number (varAddTwo+2). public void AddTwo() { double varTotalAsOfNow = 0; int varResult = varAddTwo + 2; varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; AddTwoComplete(varResult, varTotalAsOfNow); } // This method will run a loop with a nested loop varLoopValue times. public void RunALoop() { int varX; double varTotalAsOfNow = 0; for (varX = 1; varX <= varLoopValue; varX++) { // This nested loop is added solely for the purpose of slowing down // the program and creating a processor-intensive application. for (int varY = 1; varY <= 500; varY++) { varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; } } LoopComplete(varTotalAsOfNow, varLoopValue); }
Transferowanie danych wejściowych użytkownika do składnika
Następnym krokiem jest dodanie kodu do frmCalculations do odbierania danych wejściowych od użytkownika oraz do transferu i otrzymują wartości do i z Calculator składnika.
Aby zaimplementować frontonu funkcji frmCalculations
Otwórz frmCalculations w Edytor kodu.
Zlokalizuj public partial class frmCalculations instrukcji.Bezpośrednio pod { typu:
Calculator Calculator1;
Zlokalizuj konstruktora.Bezpośrednio przed }, Dodaj następujący wiersz:
// Creates a new instance of Calculator. Calculator1 = new Calculator();
W projektancie kliknij każdy przycisk do generowania kodu konspektu każdego formantu Click zdarzeń i dodać kod obsługi.
Po zakończeniu programu Click zdarzeń powinny być podobne do następujących:
// Passes the value typed in the txtValue to Calculator.varFact1. private void btnFactorial1_Click(object sender, System.EventArgs e) { Calculator1.varFact1 = int.Parse(txtValue.Text); // Disables the btnFactorial1 until this calculation is complete. btnFactorial1.Enabled = false; Calculator1.Factorial(); } private void btnFactorial2_Click(object sender, System.EventArgs e) { Calculator1.varFact2 = int.Parse(txtValue.Text); btnFactorial2.Enabled = false; Calculator1.FactorialMinusOne(); } private void btnAddTwo_Click(object sender, System.EventArgs e) { Calculator1.varAddTwo = int.Parse(txtValue.Text); btnAddTwo.Enabled = false; Calculator1.AddTwo(); } private void btnRunLoops_Click(object sender, System.EventArgs e) { Calculator1.varLoopValue = int.Parse(txtValue.Text); btnRunLoops.Enabled = false; // Lets the user know that a loop is running lblRunLoops.Text = "Looping"; Calculator1.RunALoop(); }
Po kodzie dodane w poprzednim kroku, należy wpisać następujące do obsługi zdarzeń formularza będzie otrzymywać od Calculator1:
private void FactorialHandler(double Value, double Calculations) // Displays the returned value in the appropriate label. { lblFactorial1.Text = Value.ToString(); // Re-enables the button so it can be used again. btnFactorial1.Enabled = true; // Updates the label that displays the total calculations performed lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } private void FactorialMinusHandler(double Value, double Calculations) { lblFactorial2.Text = Value.ToString(); btnFactorial2.Enabled = true; lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } private void AddTwoHandler(int Value, double Calculations) { lblAddTwo.Text = Value.ToString(); btnAddTwo.Enabled = true; lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); } private void LoopDoneHandler(double Calculations, int Count) { btnRunLoops.Enabled = true; lblRunLoops.Text = Count.ToString(); lblTotalCalculations.Text = "TotalCalculations are " + Calculations.ToString(); }
W konstruktorze frmCalculations, Dodaj następujący kod bezpośrednio przed } do obsługi zdarzenia niestandardowego formularza będzie otrzymywać od Calculator1.
Calculator1.FactorialComplete += new Calculator.FactorialCompleteHandler(this.FactorialHandler); Calculator1.FactorialMinusOneComplete += new Calculator.FactorialCompleteHandler(this.FactorialMinusHandler); Calculator1.AddTwoComplete += new Calculator.AddTwoCompleteHandler(this.AddTwoHandler); Calculator1.LoopComplete += new Calculator.LoopCompleteHandler(this.LoopDoneHandler);
Testowanie aplikacji
Tworzony projekt formularza i składnik zdolny do kilku złożonych obliczeń.Chociaż możliwości wielowątkowości nie zostały jeszcze zaimplementowane, spowoduje przetestowanie projektu sprawdzić jego funkcjonalności, przed kontynuowaniem.
Aby przetestować projekt
Z debugowania menu wybierz Uruchomić debugowanie.
Aplikacja zaczyna i frmCalculations pojawi się.
W polu tekstowym wpisz 4, następnie kliknij przycisk o nazwie Dodać dwa.
Cyfrę "6" powinien być wyświetlany w etykiecie poniżej i "1 są obliczenia całkowitej" powinien być wyświetlany w lblTotalCalculations.
Teraz kliknij przycisk o nazwie silnia - 1.
Cyfrę "6" powinien być wyświetlany poniżej przycisku i lblTotalCalculations teraz odczytuje "Są obliczenia całkowitej 4."
Zmień wartość w polu tekstowym, aby 20, następnie kliknij przycisk o nazwie silnia.
Poniżej, jest wyświetlany numer "2.43290200817664E + 18" i lblTotalCalculations teraz odczytuje "Sumy są 24."
Zmień wartość w polu tekstowym 50000, a następnie kliknij przycisk oznaczony Uruchomić pętli a.
Uwaga jest niewielki, ale zauważalne interwał przed tego przycisku zostanie ponownie włączony.Etykieta przycisku tego należy przeczytać "50000" i obliczania sum, wyświetlane są "25000024".
Zmień wartość w polu tekstowym, aby 5000000 i kliknij przycisk z etykietą Uruchomić pętli a, niezwłocznie kliknij przycisk z etykietą Dodać dwa.Kliknij ponownie.
Przycisk nie odpowie, ani formantu w formularzu odpowie zakończenia pętli.
Uruchomienie programu jednym wątku wykonywania obliczeń procesor, jak w powyższym przykładzie mają tendencję do blokuje program, do momentu zakończenia obliczeń.W następnej sekcji zostaną dodane wielowątkowości zdolności do aplikacji tak, aby wiele wątków może uruchomić jednocześnie.
Dodanie możliwości wielowątkowość
Poprzedni przykład wykazać ograniczenia aplikacje uruchamiane w jednym wątku wykonywania.W następnej sekcji będzie używać Thread klasy, aby dodać wiele wątków wykonanie do składnika.
Aby dodać podprocedury wątków
Otwórz Calculator.cs w Edytor kodu.
W górnej części kodu zlokalizować deklaracji klasy i tuż poniżej {, wpisz:
// Declares the variables you will use to hold your thread objects. public System.Threading.Thread FactorialThread; public System.Threading.Thread FactorialMinusOneThread; public System.Threading.Thread AddTwoThread; public System.Threading.Thread LoopThread;
Bezpośrednio przed końcem deklaracji klasy u dołu kod Dodaj następującą metodę:
public void ChooseThreads(int threadNumber) { // Determines which thread to start based on the value it receives. switch(threadNumber) { case 1: // Sets the thread using the AddressOf the subroutine where // the thread will start. FactorialThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.Factorial)); // Starts the thread. FactorialThread.Start(); break; case 2: FactorialMinusOneThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.FactorialMinusOne)); FactorialMinusOneThread.Start(); break; case 3: AddTwoThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.AddTwo)); AddTwoThread.Start(); break; case 4: LoopThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.RunALoop)); LoopThread.Start(); break; } }
Gdy Thread jest uruchomiony, wymaga argumentu w postaci ThreadStart.ThreadStart Jest pełnomocnika, który wskazuje adres metody, gdy wątek jest zacząć.A ThreadStart nie może mieć parametrów lub przebiegu, wartości i dlatego można tylko wskazać void metody.ChooseThreads Otrzymają wartości z programu to wywołanie i wartości można używać do określenia odpowiedniego wątku, aby uruchomić tylko zaimplementowane metody.
Aby dodać odpowiedni kod frmCalculations
Otwórz frmCalculations.cs pliku w Edytor kodu, zlokalizuj private void btnFactorial1_Click.
Komentarz linii, który wywołuje Calculator1.Factorial1 metoda bezpośrednio, jak pokazano:
// Calculator1.Factorial()
Dodaj następujący wiersz do wywołania Calculator1.ChooseThreads metody:
// Passes the value 1 to Calculator1, thus directing it to start the // correct thread. Calculator1.ChooseThreads(1);
Wprowadzić zmiany podobne do drugiej button_click metody.
[!UWAGA]
Aby dołączyć odpowiednią wartość dla Threads argumentu.
Po zakończeniu, kod powinno wyglądać podobnie do następującego:
private void btnFactorial1_Click(object sender, System.EventArgs e) // Passes the value typed in the txtValue to Calculator.varFact1 { Calculator1.varFact1 = int.Parse(txtValue.Text); // Disables the btnFactorial1 until this calculation is complete btnFactorial1.Enabled = false; // Calculator1.Factorial(); Calculator1.ChooseThreads(1); } private void btnFactorial2_Click(object sender, System.EventArgs e) { Calculator1.varFact2 = int.Parse(txtValue.Text); btnFactorial2.Enabled = false; // Calculator1.FactorialMinusOne(); Calculator1.ChooseThreads(2); } private void btnAddTwo_Click(object sender, System.EventArgs e) { Calculator1.varAddTwo = int.Parse(txtValue.Text); btnAddTwo.Enabled = false; // Calculator1.AddTwo(); Calculator1.ChooseThreads(3); } private void btnRunLoops_Click(object sender, System.EventArgs e) { Calculator1.varLoopValue = int.Parse(txtValue.Text); btnRunLoops.Enabled = false; // Lets the user know that a loop is running lblRunLoops.Text = "Looping"; // Calculator1.RunALoop(); Calculator1.ChooseThreads(4); }
Kierowanie połączeń do formantów
Teraz ułatwi aktualizacją wyświetlania w formularzu.Ponieważ formanty są zawsze własnością głównego wątku wykonywania, każde wywołanie formantu z wątku podrzędnego wymaga kierowanie wywołania.Kierowanie jest aktem przenoszenia wywołania przez granice wątków i jest bardzo kosztowne w odniesieniu do zasobów.Aby zminimalizować ilość kierowanie musi wystąpić i w sposób bezpieczny wątek obsługi wywołania upewnij się, będą używać Control.BeginInvoke metody wywołania metod w głównym wątku wykonywania zminimalizować ilość cross wątek granice organizacyjne musi wystąpić.Konieczne jest wywołanie tego rodzaju podczas wywoływania metody manipulujące formantów.Aby uzyskać szczegółowe informacje, zobacz Porady: manipulowanie kontrolkami pochodzącymi z wątków.
Aby utworzyć wywoływanie kontroli procedury
Otwórz Edytor kodu dla frmCalculations.W sekcji deklaracji Dodaj następujący kod:
public delegate void FHandler(double Value, double Calculations); public delegate void A2Handler(int Value, double Calculations); public delegate void LDHandler(double Calculations, int Count);
Invokei BeginInvoke wymagają pełnomocnika do odpowiedniej metody jako argumentu.Te wiersze zadeklarować pełnomocnika podpisy, które będą używane przez BeginInvoke do wywołania właściwych metod.
Dodaj następujące metody pusty do kodu.
public void FactHandler(double Value, double Calculations) { } public void Fact1Handler(double Value, double Calculations) { } public void Add2Handler(int Value, double Calculations) { } public void LDoneHandler(double Calculations, int Count) { }
Z Edytuj menu Użyj Wytnij i Wklej do wycinania cały kod z metody FactorialHandler i wklej go do FactHandler.
Repeat the previous step for FactorialMinusHandler and Fact1Handler, AddTwoHandler and Add2Handler, and LoopDoneHandler and LDoneHandler.
Po zakończeniu powinny być żadnego kodu pozostające w FactorialHandler, Factorial1Handler, AddTwoHandler, i LoopDoneHandleri wszystkie te powinien zawierać powinno zostały przeniesione do odpowiednich nowych metod kodu.
Wywołanie BeginInvoke metody do wywołania metody asynchronicznie.Można wywołać BeginInvoke z jednego formularza (this) lub formanty formularza.
Po zakończeniu kodu powinno wyglądać podobnie do następującego:
protected void FactorialHandler(double Value, double Calculations) { // BeginInvoke causes asynchronous execution to begin at the address // specified by the delegate. Simply put, it transfers execution of // this method back to the main thread. Any parameters required by // the method contained at the delegate are wrapped in an object and // passed. this.BeginInvoke(new FHandler(FactHandler), new Object[] {Value, Calculations}); } protected void FactorialMinusHandler(double Value, double Calculations) { this.BeginInvoke(new FHandler(Fact1Handler), new Object [] {Value, Calculations}); } protected void AddTwoHandler(int Value, double Calculations) { this.BeginInvoke(new A2Handler(Add2Handler), new Object[] {Value, Calculations}); } protected void LoopDoneHandler(double Calculations, int Count) { this.BeginInvoke(new LDHandler(LDoneHandler), new Object[] {Calculations, Count}); }
Może ona wydawać, że obsługi zdarzeń jest po prostu nawiązywania połączenia do następnej metody.Metoda powoływać się w głównym wątku operacji faktycznie powoduje obsługi zdarzeń.Podejście to zapisuje na wywołania przez granice wątków i pozwala skutecznie i bez obawy powoduje blokowanie aplikacji wielowątkowych.Szczegółowe informacje dotyczące pracy z formantów w środowisku wielowątkowym Porady: manipulowanie kontrolkami pochodzącymi z wątków.
Zapisz swoją pracę.
Sprawdź swoje rozwiązanie, wybierając Uruchomić debugowanie z debugowania menu.
Typ 10000000 w polu tekstowym i kliknij Uruchomić pętli a.
"Pętli" jest wyświetlany w etykiecie poniżej tego przycisku.Ta pętla powinny podjąć znaczną ilość czasu na uruchomienie.Wykona on zbyt wcześnie, należy odpowiednio dostosować rozmiar liczby.
W krótkim odstępie czasu kliknij wszystkie trzy przyciski, które są nadal włączone.Znajdziesz, że wszystkie przyciski odpowiadać na dane wejściowe.Etykieta pod Dodać dwa powinny być najpierw do wyświetlenia wyniku.Wyniki wyświetlane są później w etykietach poniżej przycisków silni.Te wyniki oceny do nieskończoności, jako liczba zwracanych przez 10 000 000 silnia jest zbyt duży dla zmiennej podwójnej precyzji zawierają.Wreszcie, opóźnieniem dodatkowe wyniki są zwracane pod Uruchomić pętli a przycisku.
Po prostu obserwowane, czterech oddzielnych zestawów obliczenia były wykonywane jednocześnie na czterech oddzielnych wątków.Interfejs użytkownika pozostała odpowiadać wejściowe i wyniki zostały zwrócone po każdego wątku zakończone.
Koordynowanie wątkom
Doświadczonych użytkowników, aplikacji wielowątkowych mogą postrzegać subtelnych usterka kod wpisanych.Odwołaj wiersze kodu pochodzące z każdej metody wykonywania obliczeń w Calculator:
varTotalCalculations += 1;
varTotalAsOfNow = varTotalCalculations;
Te dwa wiersze kodu, zwiększ wartość zmiennej publicznej varTotalCalculations i zmienna lokalna varTotalAsOfNow tej wartości.Ta wartość jest zwracany do frmCalculations i wyświetlane w formancie etykiety.Ale jest zwracany poprawny?Jeśli tylko pojedynczego wątku wykonywania jest uruchomiony, odpowiedź jest wyraźnie tak.Ale jeśli korzystasz z wielu wątków, odpowiedź staje się bardziej niepewna.Każdy wątek ma możliwość Zwiększ wartość zmiennej varTotalCalculations.Możliwe, że po jednym wątku zwiększa tej zmiennej, ale przed kopiuje wartość varTotalAsOfNow, inny wątek może zmienić wartość tej zmiennej, zwiększając ją.Prowadzi to możliwość, że każdy wątek w rzeczywistości raportowania niedokładnych wyników.Visual C#zawiera lock — Instrukcja (odwołanie w C#) , aby umożliwić synchronizacji wątków, aby zapewnić, że każdy wątek ma zawsze zwraca dokładny wynik.Składnia lock jest następujący:
lock(AnObject)
{
// Insert code that affects the object.
// Insert more code that affects the object.
// Insert more code that affects the object.
// Release the lock.
}
Gdy lock jest wprowadzana w bloku, wykonanie na określone wyrażenie jest zablokowana, dopóki określony wątek ma wyłączną blokadę na dany obiekt.W powyższym przykładzie wykonywanie jest zablokowany na AnObject.lockmusi być używana z obiektu, który zwraca odwołanie niż wartość.Następnie kontynuować wykonywanie jako blok bez zakłóceń z innych wątków.Zestaw instrukcji, które wykonywane jako jednostka są określane jako atomic.Gdy } jest napotkał, wyrażenie jest zwalniany i wątki mogą kontynuować normalnie.
Aby dodać instrukcję blokady aplikacji
Otwórz Calculator.cs w Edytor kodu.
Zlokalizuj każde wystąpienie następujący kod:
varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations;
Powinny być cztery wystąpienia tego kodu, jeden w każdej metodzie obliczeń.
Zmodyfikować ten kod, tak aby otrzymuje brzmienie:
lock(this) { varTotalCalculations += 1; varTotalAsOfNow = varTotalCalculations; }
Zapisz swoją pracę i przetestować w poprzednim przykładzie.
Można zauważyć niewielki wpływ na wydajność programu.Jest to spowodowane uzyskiwane są wyłączną blokadę na składnik przerywa wykonywanie wątków.Chociaż zapewnia dokładność, podejście to utrudnia niektórych zalet wydajności wiele wątków.Należy uważnie rozważyć potrzebę blokowania wątków i je tylko wtedy, gdy jest to absolutnie konieczne.
Zobacz też
Zadania
Porady: koordynowanie wielu wątków wykonania
Wskazówki: tworzenie prostego składnika wielowątkowego za pomocą języka Visual Basic
Informacje
Koncepcje
Asynchroniczny wzorzec oparty na zdarzeniach — przegląd
Inne zasoby
Programowanie przy użyciu składników