Рейтинговые книги
Читем онлайн Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 179 180 181 182 183 184 185 186 187 ... 407

В главе 14 поток определялся как путь выполнения внутри исполняемого приложения. Хотя многие приложения .NET Core могут успешно и продуктивно работать, будучи однопоточными, первичный поток сборки (создаваемый исполняющей средой при выполнении точки входа приложения) в любое время может порождать вторичные потоки для выполнения дополнительных единиц работы. За счет создания дополнительных потоков можно строить более отзывчивые (но не обязательно быстрее выполняющиеся на одноядерных машинах) приложения.

Пространство имен System.Threading появилось в версии .NET 1.0 и предлагает один из подходов к построению многопоточных приложений. Равным типом в этом пространстве имен можно назвать, пожалуй, класс Thread, поскольку он представляет отдельный поток. Если необходимо программно получить ссылку на поток, который в текущий момент выполняет заданный член, то нужно просто обратиться к статическому свойству Thread.CurrentThread:

static void ExtractExecutingThread()

{

  // Получить поток, который в настоящий момент выполняет данный метод.

  Thread currThread = Thread.CurrentThread;

}

Вспомните, что в .NET Core существует только один домен приложения. Хотя создавать дополнительные домены приложений нельзя, домен приложения может иметь многочисленные потоки, выполняющиеся в каждый конкретный момент времени. Чтобы получить ссылку на домен приложения, который обслуживает приложение, понадобится вызвать статический метод Thread.GetDomain():

static void ExtractAppDomainHostingThread()

{

  // Получить домен приложения, обслуживающий текущий поток.

  AppDomain ad = Thread.GetDomain();

}

Одиночный поток в любой момент также может быть перенесен в контекст выполнения и перемещаться внутри нового контекста выполнения по прихоти среды .NET Core Runtime. Для получения текущего контекста выполнения, в котором выполняется поток, используется статическое свойство Thread.CurrentThread.ExecutionContext:

static void ExtractCurrentThreadExecutionContext()

{

  // Получить контекст выполнения, в котором работает текущий поток.

  ExecutionContext ctx =

    Thread.CurrentThread.ExecutionContext;

}

Еще раз: за перемещение потоков в контекст выполнения и из него отвечает среда .NET Core Runtime. Как разработчик приложений .NET Core, вы всегда остаетесь в блаженном неведении относительно того, где завершается каждый конкретный поток. Тем не менее, вы должны быть осведомлены о разнообразных способах получения лежащих в основе примитивов.

Сложность, связанная с параллелизмом

Один из многих болезненных аспектов многопоточного программирования связан с ограниченным контролем над тем, как операционная система или исполняющая среда задействует потоки. Например, написав блок кода, который создает новый поток выполнения, нельзя гарантировать, что этот поток запустится немедленно. Взамен такой код только инструктирует операционную систему или исполняющую среду о необходимости как можно более скорого запуска потока (что обычно происходит, когда планировщик потоков добирается до него).

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

Чтобы проиллюстрировать проблему, давайте предположим, что поток вызывает метод специфичного объекта. Теперь представим, что поток приостановлен планировщиком потока, чтобы позволить другому потоку обратиться к тому же методу того же самого объекта.

Если исходный поток не завершил свою операцию, тогда второй входящий поток может увидеть объект в частично модифицированном состоянии. В таком случае второй поток по существу читает фиктивные данные, что определенно может привести к очень странным (и трудно обнаруживаемым) ошибкам, которые еще труднее воспроизвести и устранить.

С другой стороны, атомарные операции в многопоточной среде всегда безопасны. К сожалению, в библиотеках базовых классов .NET Core есть лишь несколько гарантированно атомарных операций. Даже действие по присваиванию значения переменной-члену не является атомарным! Если только в документации по .NET Core специально не сказано об атомарности операции, то вы обязаны считать ее изменчивой в потоках и предпринимать соответствующие меры предосторожности.

Роль синхронизации потоков

К настоящему моменту должно быть ясно, что многопоточные программы сами по себе довольно изменчивы, т.к. многочисленные потоки могут оперировать разделяемыми ресурсами (более или менее) одновременно. Чтобы защитить ресурсы приложений от возможного повреждения, разработчики приложений .NET Core должны применять потоковые примитивы (такие как блокировки, мониторы, атрибут [Synchronization] или поддержка языковых ключевых слов) для управления доступом между выполняющимися потоками.

Несмотря на то что платформа .NET Core не способна полностью скрыть сложности, связанные с построением надежных многопоточных приложений, сам процесс был значительно упрощен. Используя типы из пространства имен System.Threading, библиотеку TPL и ключевые слова async и await языка С#, можно работать с множеством потоков, прикладывая минимальные усилия.

Прежде чем погрузиться в детали пространства имен System.Threading, библиотеки TPL и ключевых слов async и await языка С#, мы начнем с выяснения того, каким образом можно применять тип делегата .NET Core для вызова метода в асинхронной манере. Хотя вполне справедливо утверждать, что с выходом версии .NET 4.6 ключевые слова async и await предлагают более простую альтернативу асинхронным делегатам, по-прежнему важно знать способы взаимодействия с кодом, использующим этот подход (в производственной среде имеется масса кода, в котором применяются асинхронные делегаты).

Пространство имен System.Threading

В рамках платформ .NET и .NET Core пространство имен System.Threading предоставляет типы, которые дают возможность напрямую конструировать многопоточные приложения. В дополнение к типам, позволяющим взаимодействовать с потоком .NET Core Runtime, в System.Threading определены типы, которые открывают доступ к пулу потоков, обслуживаемому .NET Core Runtime, простому (не связанному с графическим пользовательским интерфейсом) классу Timer и многочисленным типам, применяемым для синхронизированного доступа к разделяемым ресурсам.

В табл. 15.1 перечислены некоторые важные члены пространства имен System.Threading. (За полными сведениями обращайтесь в документацию по .NET Core.)

Класс System.Threading.Thread

Класс Thread является самым элементарным из всех типов в пространстве имен System.Threading. Он представляет объектно-ориентированную оболочку вокруг заданного пути выполнения внутри отдельного домена приложения. В этом классе определено несколько методов (статических и уровня экземпляра), которые позволяют создавать новые потоки внутри текущего домена приложения, а также приостанавливать, останавливать и уничтожать указанный поток. Список основных статических членов приведен в табл. 15.2.

Класс Thread также поддерживает члены уровня экземпляра, часть которых описана в табл. 15.3.

На заметку! Прекращение работы или приостановка активного потока обычно считается плохой идеей. В таком случае есть шанс (хотя

1 ... 179 180 181 182 183 184 185 186 187 ... 407
На этой странице вы можете бесплатно читать книгу Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен бесплатно.

Оставить комментарий