Задачи (класс Task)

86

В основу TPL положен класс Task. Элементарная единица исполнения инкапсулируется в TPL средствами класса Task, а не Thread. Класс Task отличается от класса Thread тем, что он является абстракцией, представляющей асинхронную операцию. А в классе Thread инкапсулируется поток исполнения. Разумеется, на системном уровне поток по-прежнему остается элементарной единицей исполнения, которую можно планировать средствами операционной системы. Но соответствие экземпляра объекта класса Task и потока исполнения не обязательно оказывается взаимно-однозначным.

Кроме того, исполнением задач управляет планировщик задач, который работает с пулом потоков. Это, например, означает, что несколько задач могут разделять один и тот же поток. Класс Task (и вся остальная библиотека TPL) определены в пространстве имен System.Threading.Tasks.

Создание задачи

Создать новую задачу в виде объекта класса Task и начать ее исполнение можно самыми разными способами. Для начала создадим объект типа Task с помощью конструктора и запустим его, вызвав метод Start(). Для этой цели в классе Task определено несколько конструкторов. Ниже приведен тот конструктор, которым мы собираемся воспользоваться:

public Task(Action действие)

где действие обозначает точку входа в код, представляющий задачу, тогда как Action — делегат, определенный в пространстве имен System. Форма делегата Action, которой мы собираемся воспользоваться, выглядит следующим образом:

public delegate void Action()

Таким образом, точкой входа должен служить метод, не принимающий никаких параметров и не возвращающий никаких значений. (Как будет показано далее, делегату Action можно также передать аргумент.)

Как только задача будет создана, ее можно запустить на исполнение, вызвав метод Start(). После вызова метода Start() планировщик задач запланирует исполнение задачи.

В приведенной ниже программе все изложенное выше демонстрируется на практике. В этой программе отдельная задача создается на основе метода MyTask(). После того как начнет выполняться метод Main(), задача фактически создается и запускается на исполнение. Оба метода MyTask() и Main() выполняются параллельно:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        // Метод выполняемый в качестве задачи
        static void MyTask()
        {
            Console.WriteLine("MyTask() запущен");

            for (int count = 0; count < 10; count++)
            {
                Thread.Sleep(500);
                Console.WriteLine("В методе MyTask подсчет равен "+count);
            }
        }

        static void Main()
        {
            Console.WriteLine("Основной поток запущен");

            Task task = new Task(MyTask);

            // Запустить задачу
            task.Start();

            for (int i = 0; i < 60; i++)
            {
                Console.Write(".");
                Thread.Sleep(100);
            }

            Console.WriteLine("Основной поток завершен");
            Console.ReadLine();
        }
    }
}
Пример запуска задачи

Следует иметь в виду, что по умолчанию задача исполняется в фоновом потоке. Следовательно, при завершении создающего потока завершается и сама задача. Именно поэтому в рассматриваемой здесь программе метод Thread.Sleep() использован для сохранения активным основного потока до тех пор, пока не завершится выполнение метода MyTask(). Как и следовало ожидать, организовать ожидание завершения задачи можно и более совершенными способами.

В отношении задач необходимо также иметь в виду следующее: после того, как задача завершена, она не может быть перезапущена. Следовательно, иного способа повторного запуска задачи на исполнение, кроме создания ее снова, не существует.

Применение идентификатора задачи

В отличие от класса Thread, в классе Task отсутствует свойство Name для хранения имени задачи. Но вместо этого в нем имеется свойство Id для хранения идентификатора задачи, по которому можно распознавать задачи. Свойство Id доступно только для чтения и относится к типу int.

Каждая задача получает идентификатор, когда она создается. Значения идентификаторов уникальны, но не упорядочены. Поэтому один идентификатор задачи может появиться перед другим, хотя он может и не иметь меньшее значение.

Идентификатор исполняемой в настоящий момент задачи можно выявить с помощью свойства CurrentId. Это свойство доступно только для чтения, относится к типу static и объявляется следующим образом:

public static Nullable<int> CurrentId { get; }

Оно возвращает исполняемую в настоящий момент задачу или же пустое значение, если вызывающий код не является задачей.

В приведенном ниже примере программы создаются две задачи и показывается, какая из них исполняется:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        // Метод выполняемый в качестве задачи
        static void MyTask()
        {
            Console.WriteLine("MyTask() №{0} запущен",Task.CurrentId);

            for (int count = 0; count < 10; count++)
            {
                Thread.Sleep(500);
                Console.WriteLine("В методе MyTask №{0} подсчет равен {1}",Task.CurrentId,count);
            }
        }

        static void Main()
        {
            Console.WriteLine("Основной поток запущен");

            Task task1 = new Task(MyTask);
            Task task2 = new Task(MyTask);

            // Запустить задачу
            task1.Start();
            task2.Start();

            for (int i = 0; i < 60; i++)
            {
                Console.Write(".");
                Thread.Sleep(100);
            }

            Console.WriteLine("Основной поток завершен");
            Console.ReadLine();
        }
    }
}
Создание нескольких задач
Пройди тесты
Лучший чат для C# программистов