Task Parallel Library – Створення паралельних завдань

Доброго дня! Давно я не писав на своєму блозі, через те, що на даний момент я не маю можливості ставити плагіни для підсвічування коду і бажання відпало писати код, який не дуже легко читати читачам блогу. Але все-таки бажання з’явилось, коли Сергій Байдачний сказав почекати тиждень, і тоді вже буде якась інформація щодо безкоштовного хостингу для студентів. Поки користуємось тим, що маємо.

В попередніх статтях я писав про створення в Task Parallel Library циклів for і foreach, та деяку маніпуляцію з ними.

Отже в даній статті я напишу про деякі можливості, які нам надає TPL, і які безпосередньо пов’язані зі створенням та маніпулюванням завданнями, які можна запускати паралельно.

clip_image001

Завдання (Task) являє собою одиницю роботи, яка має виконатись в певний момент часу, але просто роботу, яку потрібно виконувати ми можемо запустити і без TPL, тому ціль створення завдання (Task) є такою, що в розробник створює кілька завдань, які будуть виконуватись асинхронно.

Основні переваги використання TPL:

· Більш ефективне використання апаратних ресурсів, яке автоматично визначається на етапі виконання

· Розширена можливість керування задачами у порівнянні зі старими методиками (Thread, ThreadPool)

Отже перейдемо до конкретних прикладів.

Створення завдання

Самий простий спосіб створити завдання та запустити його – це використання статичного методу Invoke класу Parallel:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ParallelProgrammingTasks
{
    class Program
    {
        static void Main(string[] args)
        {
            Parallel.Invoke(() => SomeJob(), () => AnotherJob());
        }
        private static void SomeJob()
        {
            Console.WriteLine("The job has been done");
        }
        private static void AnotherJob()
        {
            Console.WriteLine("Another job has been done");
        }
    }
}

Цей метод набір делегатів Action, які викликають певні методи. Для тих, хто ще не знайомий з делегатами, можете почитати оцю статтю, в якій описується концепція делегування в .NET та C#. Про анонімні методи та лямбда вирази можете почитати на сайті MSDN.

Розглянемо інший спосіб створення завдань:

static void Main(string[] args)
{
    Task firstTask = new Task(() => SomeJob());
    Task secondTask = new Task(() => AnotherJob());
    firstTask.Start();
    secondTask.Start();
    Console.WriteLine("Main thread completed execution");
    Console.ReadLine();
}

Отримаємо такий результат виводу:

clip_image002

З цього може виникнути питання: Який спосіб краще використовувати і чим перший відрізняється від другого? Відповідь очевидна, в першому випадку ви лише запускаєте завдання, коли в другому ви можете цим завданням маніпулювати та отримувати певний результат щодо його виконання.

Отримання результату виконання завдання

Отже припустимо, що в нас є завдання, яке має зробити певні обрахунки, які будуть виконуватись одночасно в кількох потоках. Для того, щоб це здійснити, в нашому розпорядженні є клас Task<T> де T – результат завдання. В наступному прикладі я продемонструю використання Task<T> разом з використанням TaskFactory:

static void Main(string[] args)
{
    Task<Double> firstTask = Task.Factory.StartNew<Double>(() =>
    {
        return Math.Sqrt(2048) + Math.Pow(10, 10) ;
    });
    Task<Double> secondTask = Task.Factory.StartNew<Double>(() =>
    {
        return Math.Log(Math.Sqrt(2048) + Math.Pow(10, 10));
     });
    Console.WriteLine(firstTask.Result);
    Console.WriteLine(secondTask.Result);
    Console.WriteLine("Main thread completed execution");
    Console.ReadLine();
}

Почну з опису статичного методу StartNew<T> класу TaskFactory. Цей метод надає можливість створивши завдання відразу його запустити, що є корисним в тому випадку, коли нам не потрібен контроль над запуском завдань, і що призведе до зменшення кількості коду. В гострих дужках я передаю тип результату, який має повернути завдання, а сам метод StartNew повертає об’єкт Task<T> де T знову ж таки результат виконання. В тілі виконання лямбда виразу робляться довільні обчислення, які і є саме результатом завдання, і в кінці два рядки коду доступаються до результату через властивість Result екземпляру класу Task<T>.

У зацікавлених розробників однозначно виникне запитання. А що буде якщо завдання ще не було виконано, а головний потік пробує доступитись до результату? Відповідь очевидна – йому доведеться дочекатись цього виконання.

clip_image003

Отже я описав лише кілька механізмів, які пов’язані з завданнями (Task) в TPL. Так як функціональності ще дуже багато, то я буду її описувати в наступних статтях. До Зустрічі! Smile

Advertisements

, ,

  1. #1 by ZuTa on July 26, 2011 - 08:28

    А що, якщо я створю Task на досить важку роботу, яка буде виконуватися тривалий час і мені потрібно буде отримати від цього Task результат. Як мені поступити, щоб не зупинити головний потік? (адже “..доведеться дочекатись цього виконання.”)

  2. #2 by Serhiy Shumakov on July 26, 2011 - 15:38

    Для такого випадку використовуйте метод ContinueWith. Ось модифікований кусок коду, який доступається до результату завдання одразу після його завершення:

    firstTask.ContinueWith( task =>
    {
          Console.WriteLine(task.Result);
    });
    secondTask.ContinueWith(task =>
    {
          Console.WriteLine(task.Result);
    });
    
  3. #3 by ZuTa on August 3, 2011 - 15:54

    Зрозуміло. дякую!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: