sobota, 9 lutego 2013

[C#|Visual Studio] Threading: Thread Pool

Pula wątków rozwiązuje dwa dosyć poważne problemy związane ze współbieżnością w .NET:
  • każdy wątek domyślnie wykorzystuje około 1MB pamięci
  • utworzenie nowego wątku wraz z organizacją pamięci dla niego trwa setki milisekund
Dzięki puli wątków zyskujemy automatyczne współdzielenie i odzyskiwanie już pracujących wątków. Ponadto kontrolowana jest maksymalna ilość wątków typu "worker" jakie mogą pracować w danej chwili. Zbyt duże ilości aktywnych wątków mogą zaburzać pracę systemu operacyjnego, co staje się niewygodne dla użytkownika. Gdy zastosuje się Thread Pool, to w przypadku przekroczenia maksymalnej liczby wątków, nowe wątki są kolejkowane i uruchamiane dopiero w momencie, gdy któreś z aktywnych wątków skończą swoją pracę.

Przed rozpoczęciem pracy z pulą wątków należy pamiętać, że będą one zawsze wątkami typu "background".

Dostęp do puli wątków można uzyskać na kilka sposobów:

Pula wątków poprzez Task Parallel Library (TPL)

TPL dostarcza specjalną klasę Task, która dostępna jest pod dwoma postaciami: generyczną i niegeneryczną. Wątek z Thread Pool można wystartować w poniższy sposób:

public static void Run()
{
    Task.Factory.StartNew(NonGenericTask);
}

public static void NonGenericTask()
{
    Console.WriteLine("This is thread from thread pool");
}

lub w sposób generyczny, gdzie możemy zwracać wartość z wątku.

public static void Run()
{
    Task<string> downloader = Task.Factory.StartNew(
        () => GenericTask("http://www.wisla.krakow.pl"));
    Console.WriteLine("Some operations...");
    var result = downloader.Result;
    Console.WriteLine(result);
}

public static string GenericTask(string uri)
{
    Console.WriteLine("This is thread from thread pool");
    using (var wc = new System.Net.WebClient())
        return wc.DownloadString(uri);
}

Warto pamiętać, że w momencie, gdy odwołujemy się do property Result, wątek główny zostanie zawieszony do momentu wywołania wątku z puli.

TPL został wprowadzony w .NET 4.0. W starszych wersjach zachodzi konieczność korzystania z puli wątków w nieco inny sposób.

Pula wątków poprzez klasę ThreadPool

Tutaj mamy do dyspozycji klasę ThreadPool, gdzie wywołując wątek możemy przez parametr podać stan (jako typ object).

public static void Run()
{
    Console.WriteLine("Without TPL");
    ThreadPool.QueueUserWorkItem(QueueUserWorkItemFcn, 
        DateTime.Today.ToShortDateString());
}

public static void QueueUserWorkItemFcn(object dateTime)
{
    Console.WriteLine("This is thread from thread pool, at {0}", dateTime);
}

Jeżeli chcemy zwracać obiekty z wątku nie używając TPL, należy wykorzystać tzw. asynchronous delegates.

public static void Run()
{
    Console.WriteLine("Asynchronous delegate");
    Func<string, string> func = new Func<string, string>(AsynchronousDelegate);
    IAsyncResult value = func.BeginInvoke("this is some text", null, null);
    Console.WriteLine("Some operations...");
    var str = func.EndInvoke(value);
    Console.WriteLine(str);
}

public static string AsynchronousDelegate(string letters)
{
    Console.WriteLine("This is thread from thread pool");
    IEnumerable<char> rev = letters.Reverse();
    string result = "";
    foreach (var @char in rev)
    {
        result += @char;
    }
    return result;
}

Operacja EndInvoke czeka aż asynchroniczny delegat wykona swoje zadanie, zawieszając główny wątek, odbiera rezultat przetwarzania, a także "przerzuca" wyjątek z wątku w tle do wątku głównego.

Wywołując BeginInvoke można jako drugi parametr przekazać callback, który wykona się po zakończeniu pracy wątku. Callback jako parametr przyjmuje typ IAsyncResult.

public static void Run()
{
    var method = new Func<string, string>(AsynchronousDelegate);
    method.BeginInvoke("My text", Callback, method);
}

public static void Callback(IAsyncResult result)
{
    var target = (Func<string, string>)result.AsyncState;
    string res = target.EndInvoke(result);
    Console.WriteLine(res);
}

Brak komentarzy:

Prześlij komentarz