Изоляция потоков

Исключение, сгенерированное одним потоком, не приведет к отказу в работе другого потока. Пример Threadlsolation демонстрирует это.
_gc class tm
// класс сборщика мусора tm
{
public:
void m() {
Console::WriteLine(
"Thread {0} started", // "Поток {0} начал работу",
Thread::CurrentThread->GetHashCode().ToString());
Thread::Sleep(1000); // Поток:: Бездействует forfint i = 0; i < 10; i++)
Console::WriteLine(i); Console::WriteLine(
"Thread {0} done",
// "Поток {0} закончил",
Thread::CurrentThread->GetHashCode{).ToString());
}
};
_gc class te
// класс сборщика мусора te
{
public:
void tue ()
{
Console::WriteLine (
"Thread {0} started", // "Поток {0} начал работу",
Thread::CurrentThread->GetHashCode().ToString());
Exception *e = new Exception("Thread Exception"); // Исключение *е = новое Исключение ("Исключение Потока"); throw e;
}
};
_gc class Threadlsolation
// класс сборщика мусора Threadlsolation
{
public:
static void Main()
{
tm *tt = new tm;
// новый tm te *tex = new te;
// новый te
// создать делегат для потоков
ThreadStart *tsl = new ThreadStart(tt, tm::m);
ThreadStart *ts2 = new ThreadStart(tex, te::tue);
Thread *threadl = new Thread(tsl);
// новый Поток (tsl) Thread *thread2 = new Thread(ts2);
// новый Поток (ts2)
Console::WriteLine(
"Thread {0} starting new threads.",
// "Поток {0} запускает новые потоки.",
Thread::CurrentThread->GetHashCode().ToString());
threadl->Start(); // Пуск
thread2->Start(); // Пуск
Console::WriteLine(
"Thiead {0} done.", // "Поток {0} закончил.",
Thread::CurrentThread->GetHashCode().ToString());
}
};
Программа генерирует следующую выдачу. Обращаем внимание читателя на то, что второй поток может продолжать выводить числа даже тогда, когда первый поток прерывается в результате появления необрабатываемого исключения. И, кроме того, может закончиться главный поток, породивший другие два потока, причем это тоже не приведет к завершению этих двух потоков.
Thread 2 starting new threads.
Thread 2 done.
Thread 5 started
Thread 6 started
Unhandled Exception: System.Exception:
Thread Exception at te.tue() in
c:\oi\netcpp\chap8\threadisolation\
threadisolation.h:line 32 0
1
2
3
4
5
6
7
8
9
Thread 5 done
Перевод такой:
Поток 2 запускает новые потоки. Поток 2 работу закончил. Поток 5 стартовал Поток 6 стартовал
Необработанное Исключение:
Система.Исключение: Исключение Потока в te.tue () в
c:\oi\netcpp\chap8\threadisolation\
threadisolation.h:line 32 О
1
2
3
4
5
6
7
8
9
Поток 5 работу закончил
Класс AppDomain (Прикладная область) (обсуждаемый в данной главе позже) позволяет устанавливать обработчик для перехвата исключения UnhandledException.

Синхронизация коллекций


Некоторые списки, например TraceListeners, являются безопасными с точки зрения работы с потоками. Во время изменения этой коллекции изменяется копия и устанавливается ссылка на копию. Большинство коллекций, подобных ArrayList (Список массивов), небезопасны при работе с потоками. Но, сделав их автоматически безопасными для работы с потоками, мы уменьшили бы эффективность работы с коллекциями, даже если бы не стоял вопрос о безопасности.
ArrayList (Список массивов) содержит статический метод Synchronization (Синхронизация) для возврата к безопасной, с точки зрения работы с потоками, версии ArrayList (Список массивов). Свойство IsSynchronized позволяет проверить, безопасна ли используемая версия ArrayList (Список массивов) с точки зрения работы с потоками. Свойство SyncRoot возвращает объект, который может использоваться для синхронизации доступа к коллекции. Это позволяет синхронизировать потоки, которые могут использовать ArrayList (Список массивов), с помощью одного и того же объекта.

Контекст


И Чтобы понять, как с помощью атрибутов среда времени выполнения реализует требования организации поточной обработки, мы должны ввести понятие контекста. Шаг 4 из примера Threading (Организация поточной обработки) состоит из того же кода, что и шаг 3, но в выдаче есть дополнительная информация:
Is the customer object a proxy? False
Is the bookings object a proxy? True
Added Boston Presidential Hotel with one room.
Thread 13 Contextld 0 launching threads.
MakeReservation: Thread 28 Contextld 0 starting.
MakeReservation: Thread 29 Contextld 0 starting.
Reserving for Customer 2 at the Boston Presidential Hotel on
12/13/2001 12:00:00
AM for 1 days
Thread 29 Contextld 1 entered Reserve.
Thread 29 finds the room is available in Broker::Reserve
Reserving for Customer 1 at the Boston Presidential Hotel on
12/12/2001 12:00:00
AM for 3 days
Thread 29 Contextld 1 left Reserve.
Thread 28 Contextld 1 entered Reserve.
Thread 28 Contextld 1 left Reserve.
Reservation for Customer 1 could not be booked
Room not available
Reservation for Customer 2 has been booked
Reservationld = 1
ReservationRate = 10000
ReservationCost = 10000
Comment = OK
Перевод такой:
Объект Клиент является агентом? Ложь
Заказывающий объект является агентом? Истина
Добавлена Бостонская Президентская гостиница с одним номером.
Поток 13 Contextld 0 запускает потоки.
MakeReservation: Поток 28 Contextld 0 стартовал.
MakeReservation: Поток 29 Contextld 0 стартовал.
Резервирование для Клиента 2 в Бостонской Президентской гостинице на
12/13/2001 12:00:00
AM в течение 1 дня
Поток 29 Contextld 1 вошел в Резерв.
Поток 29 находит, что номер доступен в Брокер::Резерв
Резервирование для Клиента 1 в Бостонской Президентской гостинице
на
12/12/2001 12:00:00
AM в течение 3 дней
Поток 29 Contextld 1 вышел из Резерва.
Поток 28 Contextld 1 вошел в Резерв.
Поток 28 Contextld 1 вышел из Резерва.
Резервирование для Клиента 1 не могло быть заказано
Номер не доступен
Резервирование для Клиента 2 было заказано
Reservationld = 1
ReservationRate = 10000
ReservationCost = 10000
Комментарий = OK
На этом последнем шаге (шаг 4) примера Threading (Организация поточной обработки) мы видим, что во время выполнения метода класса Broker (Брокер), такого как Reserve (Резерв), поток имеет идентификатор контекста Contextld, который отличается от идентификатора контекста во время выполнения какого-нибудь метода вне класса. Поток выполняется в различных контекстах. Это объясняет, почему в вышеприведенной распечатке поток 28, у которого идентификатор контекста Contextld был равен О перед вызовом метода Reserve (Резерв), изменяет свой идентификатор контекста Contextld на 1 во время выполнения данного метода. Такое поведение обусловлено применением атрибута Synchronization (Синхронизация) к классу Broker (Брокер).
Объекты класса Broker (Брокер) имеют отличные от других объектов требования во время выполнения. Поэтому доступ к объектам Broker (Брокер) должен быть синхронизирован, а доступ к другим объектам — нет. Среда, которая реализует требования какого-либо объекта во время выполнения, называется контекстом. В примере Threading (Организация поточной обработки) на шаге 3 и шаге 4 реализуются два контекста: контекст 1, в котором живет объект Broker (Брокер) и контекст 0, в котором сосуществуют все другие объекты. Каждый поток в программе будет выполняться либо в контексте 1 во время выполнения методов объекта класса Broker (Брокер), либо в контексте 0 в остальных случаях. Контекст не зависит от выполняющихся в нем потоков.
Контекст — совокупность одного или более объектов с идентичными требованиями параллелизма. Концепция контекста в .NET подобна концепции апартаментов (apartment) модели компонентных объектов Microsoft (COM) и концепции контекста в СОМ+. Вообще нельзя предсказать, что нужно делать во время выполнения в рамках заданного контекста. Это зависит от требований, предъявляемых во время выполнения. Контекст с требованиями, касающимися проведения транзакций, диктует иные действия, чем контекст, к которому такие требования не предъявляются. Или, если контекст поддерживает атрибут REQUIRED (ТРЕБУЕМЫЙ) в требованиях к синхронизации, то он отличается от контекста, поддерживающего в требованиях к синхронизации атрибут REQUIRES_NEW.
Экземпляр класса Context (Контекст), который представляет текущий контекст, можно получить с помощью статического свойства Thread: :CurrentContext. Context Id — это свойство класса Context (Контекст).

Заместители и заглушки


Как же во время выполнения удается удовлетворить различные требования различных контекстов? Когда какой-нибудь объект располагается в другом контексте (например, объект HotelBroker в экземпляре NewResevation), взамен указателя непосредственно на сам объект, возвращается указатель на некоторый заместитель. Реальный объект постоянно находится в своем первоначальном контексте. Заместитель — объект, который представляет первоначальный объект в другом контексте. Статический метод Remoting-Services: : IsTransparentProxy определяет, на что ссылается указатель: на реальный объект или на заместитель. Рассмотрим код в Threading.h на шаге 4 проекта TestThreading:
bool bTrans; // логическая булева переменная
bTrans bTrans = RemotingServices::IsTransparentProxy(
customers); // клиенты
Console::WriteLine (
"Is the customer object a proxy? {0}",
// "Объект Клиент представляет заместитель? {0}",
bTrans.ToString());
bTrans = RemotingServices::IsTransparentProxy(
hotelBroker) ; Console::WriteLine(
"Is the bookings object a proxy? {0}",
// "Заказывает заместитель? {О}",
bTrans.ToString());
Этот код генерирует следующую выдачу:
Is the customer object a proxy? False Is the bookings object a proxy? True
Перевод такой:
Объект Клиент представляет заместитель? Ложь
Заказывает заместитель? Истина
При запуске программа выполняется в определенном по умолчанию контексте. Все объекты, такие как Customers (Клиенты), не предъявляющие специальных требований, создаются внутри этого контекста (контекст 0). Какой-нибудь объект, такой как HotelBroker с другими требованиями к синхронизации, создается в другом контексте (контекст 1), а заместитель возвращается в контекст 0.
Теперь, запрашивая метод MakeReservation объекта HotelBroker, мы реально получаем доступ к методу заместителя. Метод заместителя может потом применить замок синхронизации и затем делегировать функции методу реального объекта HotelBroker. Метод настоящего объекта возвращает управление заместителю. А заместитель может тогда удалить замок синхронизации и возвратить управление вызвавшей его программе. Такая технология, когда во время выполнения используются заместители для перехвата вызовов методов реальных объектов, называется перехватыванием.
Можно также просмотреть подобный пример под названием MarshalByReference в папке с примерами. Он показывает различные способы создания объекта и эффект от создания заместителя.

ContextBoundObject


Класс Broker (Брокер) должен быть производным от класса ContextBoundObject, чтобы во время выполнения можно было определить, требуется ли установить новый контекст. Если бы класс Broker (Брокер) не был производным от класса ContextBoundObject, то мы опять получили бы конфликт при организации поточной обработки: оба клиента смогли бы зарезервировать один и тот же единственный последний номер гостиницы. Это произошло бы даже несмотря на то, что класс был бы помечен атрибутом Synchronization (Синхронизация). А объект, не являющийся производным от ContextBoundOb j ect, сможет работать в любом контексте (подвижной объект).
Поскольку другие контексты работают с заместителями или ссылками на реальный объект, вызовы из одного контекста в другой необходимо преобразовывать (приспосабливать) во время выполнения. Поэтому ContextBoundObject является производным от MarshalByRefObject. MarshalByRefOb]ect — это базовый класс для объектов, которые нужно адресовать по ссылке.
Одно из преимуществ технологии синхронизации с использованием мониторов Monitor (Монитор), состоит в том, что Monitor (Монитор) может быть вызван из любого контекста. С другой стороны, потенциальный недостаток автоматической синхронизации — падение производительности из-за использования маршалинга и заместителей, по сравнению с работой с реальными объектами.
Как выяснится далее при обсуждении прикладных областей, благодаря тому, что объект клиента не зависит от контекста, именно он, а не заместитель, является тем реальным объектом, к которому осуществляется доступ. Он может быть скопирован в любой контекст внутри одной и той же прикладной области.

Изоляция приложений


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

Прикладная область


Прикладная область .NET (Application Domain, иногда называемая AppDomain) — это более простой способ изоляции приложений, безопасный и устойчивый к ошибкам. В рамках одного процесса может находиться несколько прикладных областей. Поскольку в .NET код проверяется на типовую безопасность и защищенность, общеязыковая среда времени выполнения CLR гарантирует, что прикладная область может выполняться независимо от других прикладных областей в рамках одного процесса. Для изоляции приложения переключать процессы не требуется.
В прикладной области может быть несколько контекстов, но какой-нибудь отдельный контекст может существовать только в одной прикладной области. Поток в определенный момент может выполняться только в определенном контексте определенной прикладной области. Но, как показывают шаг 3 и шаг 4 примера Threading (Организация поточной обработки), поток, вообще говоря, может выполняться в различных контекстах (более чем в одном). Один или несколько потоков могут выполняться одновременно в рамках одной прикладной области. Какой-нибудь объект может существовать только в рамках одного контекста.
Каждая прикладная область создается вместе с одним потоком в пределах одного контекста. Дополнительные потоки и контексты, в которых они выполняются, создаются по мере необходимости.
Количество прикладных областей не зависит от количества потоков. Web-сервер может требовать создания отдельной прикладной области для запуска приложения на каждый сеанс соединения в рамках одного процесса. Потоков же в этом процессе может быть гораздо меньше, причем это зависит от того, сколько потоков реально сможет поддерживать процесс.
Чтобы изолировать приложения, код из одной прикладной области не должен непосредственно вызывать кода (или даже ссылаться на ресурсы) из других прикладных областей.

Прикладные области и сборки


Приложение компонуется из одной или нескольких сборок. Но каждая сборка загружается в какую-либо прикладную область. Каждая прикладная область может быть выгружена из памяти независимо от других. Однако нельзя выгрузить отдельную сборку из какой-либо прикладной области. Сборка может быть выгружена только вместе с прикладной областью. Выгрузка той или иной прикладной области из памяти освобождает также все ресурсы, ассоциированные с этой прикладной областью.
Каждый процесс по умолчанию содержит прикладную область, которая создается при запуске процесса. Эта создаваемая по умолчанию прикладная область может быть выгружена только с завершением работы процесса.
Для приложений, подобных ASP.NET или Internet Explorer, чрезвычайно важно (критично!) предотвратить взаимное вмешательство приложений, выполняющихся в рамках таких приложений. Код приложений никогда не загружается в прикладную область, создаваемую по умолчанию. Поэтому крах какого-либо из них не приведет к краху главного приложения.

Класс AppDomain (Прикладная область)
Класс AppDomain (Прикладная область) реализует абстракцию прикладной области. Пример AppDomain (Прикладная область) иллюстрирует использование прикладных областей. Этот класс имеет статические методы для создания и разгрузки прикладных областей:
AppDomain *domain = AppDomain::CreateDomain(
"CreatedDomain2", 0, 0);
AppDomain::Unload(domain); // разгрузить область
Несмотря на то, что метод CreateDomain перегружен, следующая сигнатура иллюстрирует изоляцию прикладных областей:
static AppDomain CreateDomain( // статический
String *friendlyName, // Строка
Evidence *securitylnfo,
AppDomainSetup *info);
Параметр Evidence (Данные) — коллекция ограничений защиты для прикладной области. Этот параметр мы обсудим более подробно в главе 13 "Защита"; сейчас же мы отметим, что создатель прикладной области может модифицировать коллекцию, чтобы управлять разрешениями выполняющейся прикладной области. Параметр AppDomainSetup определяет параметры прикладной области. В параметрах указывается путь к конфигурационному файлу прикладной области и информация о том, куда загружены приватные сборки. Поэтому прикладные области могут быть сконфигурированы независимо одна от другой. Изоляция кода, изоляция параметров и управление защитой в совокупности гарантируют, что прикладные области остаются независимы друг от друга.

События AppDomain (Прикладная область)
Для поддержки изоляции приложений класс AppDomain (Прикладная область) позволяет устанавливать обработчики для следующих событий:
• выгрузка прикладной области;
• завершение процесса;
• возникновение необрабатываемого исключения;
• сбой при попытке разрешить сборки, типы или ресурсы.

Пример AppDomain (Прикладная область)
При выполнении примера AppDomain (Прикладная область) получается выдача, показанная на рис. 8.1.
Сначала выводится имя, поток и контекст прикладной области, заданной по умолчанию
AppDomain *currentDomain = AppDomain:: CurrentDomam;
Console::WriteLine(
"At startup, Default AppDomain is {0}
Threadld: {1}
"При запуске по умолчанию AppDomain - {0}
Threadld: {1}
Contextld {2}\n",
currentDomain->FrlendlyName,
Thread::CurrentThread->GetHashCode() .ToString(),
Thread::CurrentContext->ContextID.ToString());
Затем загружается и выполняется сборка Код из этой сборки только выводит строку, название прикладной области, в которую загружена сборка, а также название потока и контекста Необходимо отметить, что все это выполняется в прикладной области, создаваемой по умолчанию
int val = domain->ExecuteAssembly(
"TestApp\\bin\\Debug\\TestApp.exe", 0, args);
// параметры
Потом мы создадим экземпляр Customers (Клиенты) из сборки Customer (Клиент) в прикладной области, заданной по умолчанию Метод Createlnstance класса AppDomain (Прикладная область) возвращает экземпляр ObjectHandle Этот ObjectHandle можно передавать между прикладными областями без загрузки метаданных, ассоциированных с упакованным типом Если нужно использовать созданный экземпляр объекта, его следует распаковать, вызвав метод Unwrap (Развернуть) для объекта Ob] ectHandle
Objecthandle *on = currentDomain->Create!nstance(
"Customer", "01.NetCpp.Acme.Customers"), // Клиент
Customers *custs = // Клиенты
dynamic_cast<Customers *>(oh->Unwrap ());
Затем мы добавляем нового клиента, а потом перечисляем всех существующих клиентов Необходимо отметить, что и конструктор этого типа, и его методы выполняются в том же самом потоке и контексте, что и созданная по умолчанию прикладная область
Далее мы создаем новую прикладную область, а в ней — экземпляр того же типа, что и раньше
AppDomain *domain = AppDomain::CreateDomain(
"CreatedDomainl", 0, 0);
oh = domain->CreateInstance(
"Customer", "01.NetCpp Acme.Customers"); // Клиент
Customers *custs2 = dynamic_cast // Клиенты
<Customers *>(oh->Unwrap());
Необходимо отметить, что в результате вызова метода Createlnstance происходит вызов конструктора, который выполняется в новой прикладной области и поэтому в контексте, отличном от того, в котором был сделан вызов Createlnstance Однако выполнение этого вызова происходит в том же потоке, в котором был сделан вызов Createlnstance
Когда мы составляем список клиентов в этом новом объекте, мы получаем другой список клиентов И не удивительно, ведь это другой объект Customers (Клиенты) Тем не менее, метод составления списка клиентов выполняется в заданной по >молчанию прикладной области1
Используя RemotingServices: : IsTransparentProxy, можно увидеть, что ObjectHandle — это заместитель объекта Customers (Клиенты) в новой созданной прикладной области (AppDomain) Однако, если попытаться распаковать объект, чтобы получить дескриптор экземтяра, то в результате мы получим не указатель на заместитель, а фактическую объектную ссылку По умолчанию, объекты передаются по значению (копируются) из одной прикладной области в другую
Если объект Customers (Клиенты) — нельзя сериализовать (преобразовать в последовательную форму), то при попытке его скопировать будет запущено исключение Это исключение появляется при вызове метода Unwrap (Развернуть), а не Createlnstance Последний метод возвращает ссылку Копирование происходит только в том случае, если объект ObjectHandle распакован Если же объект не может быть преобразован в поспедоватечьную форму, он не может быть скопирован из одной прикладной области в другую
На следующем шаге будет создан новый поток, который создаст новую прикладную область, куда затем загружается и где выполняется какая-нибудь сборка Сборка начинает выполняться со своей точки входа — подпрограммы Мат (Главная) класса
AppDomainTest
AppDomain *domain = AppDomain::CreateDomain( "CreatedDomain2", 0, 0);
String * args[] = new String *[!];
// Строка * параметры [] = новая Строка * [1];
args[0] = "MakeReservation"; // параметры [О]
int val = domain->ExecuteAssembly(
"TestApp\\bin\\Debug\\TestApp.exe", 0, args); // параметры
AppDomain::Unload(domain);
Подпрограмма Main (Главная) загружает сборку Hotel (Гостиница) в созданную прикладную область. В этом примере приложение TestApp. exe реализовано на С#. После загрузки оно запрашивает метаданные сборки для получения информации о типе HotelBroker. Затем эта информация используется для создания объекта HotelBroker. Класс HotelBroker помечен атрибутом синхронизации. В результате конструктор HotelBroker и метод MakeReservation работают в контексте, отличном от заданного по умолчанию.
Assembly a = AppDomain.CurrentDomain.Load("Hotel") ;
// Сборка - Загрузить ("Гостиница"); Type typeHotelBroker =
а.GetType("01.NetCpp.Acme.HotelBroker"); HotelBroker hotelBroker =
(HotelBroker)Act vator.CreateInstance(typeHotelBroker); DateTime date = Date
Time.Parse("12/2/2001"); // дата = DateTime.
Синтаксический анализ ("12/2/2001");
ReservationResult rr = hotelBroker.MakeReservation(1,
"Boston", "Sheraton", date, 3);
// "Бостон", "Шератон", дата, 3);
Console.WriteLine("\tReservation Id: {0}", // Идентификатор
rr.Reservationld);

Маршализация, прикладные области и контексты
По умолчанию объекты копируются из одной прикладной области в другую (передаются по значению). В разделе "Удаленный доступ" показано, как происходит передача по ссылке между прикладными областями. Это гарантирует изоляцию кода одной прикладной области от кода другой.
Объекты передаются по ссылке между контекстами. Это позволяет общеязыковой среде времени выполнения CLR удовлетворять требования (например, синхронизацию или транзакции) различных объектов, независимо от того, находится ли клиент объекта в той же самой прикладной области или в другой.
Поскольку большинство объектов не являются производными от ContextBoundOb j ect, они могут постоянно храниться в одном контексте или перемещаться при необходимости из одного контекста в другой. При этом потоки могут пересекать границы прикладных областей или контекстов в пределах одного и того же процесса Win32.

Асинхронное программирование
.NET поддерживает шаблон проектирования для асинхронного программирования. Этот шаблон используется во многих местах .NET (включая операции ввода-вывода, как было отмечено ранее, и как будет более подробно показано в главе 11 "Web-службы"). Асинхронное программирование может вызывать метод без блокирования вызывающей программы метода. С точки зрения клиента использовать асинхронную модель проще, чем потоки. Однако она предоставляет гораздо меньше возможностей управления синхронизацией по сравнению с использованием синхронизирующих объектов. Поэтому разработчики классов, вероятно, сочтут, что использовать потоки намного легче.

Асинхронные шаблоны проектирования
Такой шаблон проектирования состоит из двух частей: набора методов и интерфейса lAsyncResult. Методы шаблона имеют следующий вид:
lAsyncResult *BeginXXX(
[InputParams], AsyncCallback *cb, Object *AsyncObject)
[ReturnValue] EndXXX([OutputParams], lAsyncResult *ar);
В шаблоне проектирования XXX обозначает реальный метод, который вызывается асинхронно (например, BeginRead/EndRead для класса System: : IO::FileStream). BeginXXX должен принимать те же входные параметры, что и его синхронный аналог (in, in/out и ref), а также параметры AsyncCallback и AsyncObject. В сигнатуре EndXXX должны присутствовать все выходные параметры (ref, out, in/out) синхронной версии. Этот метод должен возвратить любой объект или значение, которое возвращает синхронная версия данного метода. Он должен также иметь параметр lAsyncResult. Может быть предусмотрен и метод Cancelxxx, если в нем имеется необходимость.
AsyncCallback — делегат, который представляет функцию обратного вызова. public _delegate void AsyncCallback(lAsyncResult *ar);
AsyncObject доступен из lAsyncResult. Реализовано это так, что в функции обратного вызова можно определить, во время какого асинхронного чтения был сделан обратный вызов.
Каркас использует данный шаблон таким образом, чтобы синхронный метод Read (Чтение) класса FileStream можно было использовать асинхронно. Ниже приведено описание синхронного метода FileStream: : Read:
int Read( // Чтение
_in unsigned char* array _gc[],
int offset, int count);
// int смещение, int счетчик);
А вот асинхронная версия, используемая в шаблоне проектирования:
lAsyncResult *BeginRead(
_in unsigned char* array _gc[],
int offset, int numBytes,
// смещение,
AsyncCallback *userCallback,
Object *stateObject); // Объект
int EndRead(lAsyncResult *asyncResult);
Любое исключение, которое запускается в BeginXXX, должно быть запущено до начала выполнения асинхронных операций. Каждое исключение, связанное с асинхронными операциями, должно быть запущено из метода EndXXX.

lAsyncResult
Метод BeginXXX (такой как BeginRead) возвращает lAsyncResult. Следующий интерфейс содержит четыре элемента:
public _gc _interface lAsyncResult
// сборщик мусора - интерфейс lAsyncResult
{
public:
bool get_IsCompleted(); // логический (булев)
bool get_CompletedSynchronously();// логический (булев)
WaitHandle* get_AsyncWaitHandle();
Object* get_AsyncState(); }
Полю get_IsCompleted будет присвоено значение true (истина) после обработки вызова сервером. Клиент может уничтожить все ресурсы после того, как get_IsCompleted получит значение true (истина). Полю get_CompletedSynchronously будет присвоено значение true (истина), если BeginXXX заканчивается синхронно. Чаще всего это поле игнорируется и по умолчанию его значение устанавливается равным false (ложь). Обычно клиент даже не знает, выполняется ли метод BeginXXX синхронно или асинхронно. Если асинхронная операция не закончена, метод EndXXX блокируется до завершения выполнения операции.
Метод get_AsyncWaitHandle возвращает дескриптор WaitHandle, который можно использовать в целях синхронизации. Ранее мы видели, что этот дескриптор способен получать сигналы, так что клиент может организовать ожидание с его помощью. Поскольку можно указать период времени ожидания, то не нужно блокироваться навсегда в случае, если операция еще не закончена.
Объект get_AsyncState — последний параметр в вызове метода BeginXXX. Он позволяет дифференцировать процессы чтения в асинхронном режиме при обратном вызове.

Использование делегатов в асинхронном программировании
Каждый разработчик объектов .NET, желающий предоставить асинхронный интерфейс, должен следовать только что описанному шаблону. Однако большинству разработчиков не нужно разрабатывать собственное решение для асинхронного интерфейса к своим объектам. Делегаты обеспечивают очень простой способ поддержки асинхронных операций для любого метода без какого-либо изменения разработанных классов. Конечно, все это требует внимания, потому что при написании объекта, возможно, были сделаны некоторые предположения относительно потока, в котором он выполняется, а так-же относительно его требований к синхронизации.
Два примера Asynch используют объект Customers (Клиенты) из изученной нами сборки Customer (Клиент). Первый пример, AsynchWithoutCallback, асинхронно регистрирует новых клиентов и выполняет обработку во время ожидания завершения каждой регистрации. Второй пример, AsynchWithCallback (Asynch с обратным вызовом), использует функцию обратного вызова для асинхронной обработки данных. В дополнение к тому, что программа может обрабатывать данные во время ожидания завершения регистрации, обратный вызов позволяет системе произвести некоторое асинхронное действие для каждой отдельной регистрации.
В примерах мы только выводим информацию на консоль, чтобы показать, где могла бы быть сделана работа. Для увеличения времени ожидания, чтобы смоделировать продолжительную обработку данных, мы поместили вызовы Thread: : Sleep () (Поток::
Режим ожидания) в Customer: : RegisterCustomer и в программу-пример. Теперь рассмотрим код в примерах.
Предположим, что пользователь хочет вызвать метод RegisterCustomer асинхронно. Вызывающая программа просто объявляет делегат с той же самой сигнатурой, что и у метода.
public _delegate int RegisterCustomerCbk(
String *FirstName, // Строка
String *LastName, // Строка
String *EmailAddress); // Строка
Таким образом реальный метод превращается в функцию обратного вызова:
RegisterCustomerCbk *rcc = new
RegisterCustomerCbk{
customers, // клиенты
Customers::RegisterCustomer); // Клиенты
Вызов (invoke) начала (Begin) и конца (End)
При объявлении делегата компилятор генерирует класс с тремя методами: Beginlnvoke, Endlnvoke и Invoke (Вызвать). Beginlnvoke и Endlnvoke — методы с типовой безопасностью. Они соответствуют методам BeginXXX и EndXXX и позволяют вызывать делегат асинхронно. При вызове делегата компилятор использует метод Invoke (Вызвать)27. Чтобы асинхронно вызвать RegisterCustomer, достаточно использовать только методы Beginlnvoke и Endlnvoke.
RegisterCustomerCbk *rcc = new RegisterCustomerCbk(
customers, // клиенты
Customers::RegisterCustomer); // Клиенты for(int i = 1; i < 5; i++) {
firstName = String::Concat( // Строка
"FirstName", i.ToString ());
lastName = String::Concat( // Строка
"SecondName", (i * 2).ToString{));
emailAddress = String::Concat ( // Строка
i.ToString(), ".biz"); lAsyncResult *ar =
rcc->Begin!nvoke( firstName, lastName, emailAddress, 0, 0) ;
while(!ar->IsCompleted)
{
Console::WriteLine(
"Could do some work here while waiting for customer
registration to complete.");
// "Могу сделать некоторую работу здесь
//во время ожидания
// завершения регистрации клиента.");
ar->AsyncWaitHandle->WaitOne(1, false);
}
customerld = rcc->End!nvoke(ar) ;
Console::WriteLine(
Added Customerld: {0}",
// " Добавлен Customerld: {0}"
customerId.ToString());
}
Программа периодически ждет AsyncWaitHandle, чтобы увидеть, закончилась или нет регистрация. Если нет, то тем временем программа могла бы выполнить некоторую работу. Если Endlnvoke вызывается до завершения RegisterCustomer, то выполнение блокируется до завершения RegisterCustomer.
Асинхронный обратный вызов
Вместо ожидания на дескрипторе, можно передать функцию обратного вызова в Beginlnvoke (или BeginXXX) метод. Именно так и делается в примере AsynchWithCallback (Asynch с обратным вызовом).
RegisterCustomerCbk *rcc = new RegisterCustomerCbk( customers, // клиенты
Customers::RegisterCustomer);
// Клиенты AsyncCallback *cb =
new AsyncCallback(this, CustomerCallback);
Object *objectState; // Объект
JAsyncResult *ar; forUnt i = 5; i < 10; i++) {
firstName = String::Concat( // Строка
"FirstName", i.ToString());
lastName = String::Concat( // Строка
"SecondName", (i * 2).ToString());
emailAddress =
String::Concat(i.ToString(), ".biz"); // Строка
objectState = _box(i);
ar = rcc->Begin!nvoke( firstName,
lastName,
emailAddress,
cb,
objectState);
}
Console::WriteLine(
"Finished registrations...
could do some work here.");
// "Закончена регистрация... могу сделать
// некоторую работу здесь."
Thread::Sleep(25); // Поток:: Бездействие
Console::WriteLine(
"Finished work..waiting to let registrations complete.");
// "Закончена работа., жду конца регистрации.");
Thread::Sleep(1000); // Поток:: Бездействие
Потом мы получаем результат в функции обратного вызова:
void CustomerCallback(lAsyncResult *ar)
{
int customerld;
AsyncResult *asyncResult =
dynamic_cast<AsyncResult *>(ar);
RegisterCustomerCbk *rcc =
dynamic_cast<RegisterCustoraerCbk *> (asyncResult->AsyncDelegate);
customerld = rcc->EndInvoke(ar);
Console::WriteLine(
AsyncState: {0} Customerld {1} added.",
ar->AsyncState, customerld.ToString() ) ;
Console::WriteLine(
" Could do processing here.");
// " Могу сделать обработку здесь."
return;
}
В этом варианте можно выполнить некоторые действия до завершения регистрации каждого клиента.

Организация поточной обработки с параметрами


Асинхронный обратный вызов выполняется в потоке, отличном от того, в котором был сделан вызов Beginlnvoke. Если требования к организации поточной обработки просты, то для передачи параметров функциям потока можно использовать асинхронные делегаты. Ссылка на пространство имен Threading (Организация поточной обработки) не нужна. Ссылка на это пространство имен в примере AsynchThreading нужна только для метода Thread: : Sleep (Поток::Режим ожидания), используемого в иллюстративных целях.
PrintNumbers суммирует числа от начального целого числа, переданного в качестве параметра, до числа, на 10 большего чем начальное целое число. Результат (сумма) возвращается вызывающей программе. PrintNumbers может использоваться для делегата, определенного как Print (Печать).
//AsynchThreading.h
using namespace System;
// использование пространства имен Система;
using namespace System::Threading;
// использование пространства имен Система::Потоки;
public _delegate int Print(int i);
// делегат int Печать (int i);
public _gc class Numbers
// класс сборщика мусора - Числа
{
public:
int PrintNumbers(int start) // начало
{
int threadld = Thread::CurrentThread->GetHashCoae();
// Поток
Console::WriteLine (
"PrintNumbers Id: {0}",
// " Идентификатор PrintNumbers: {0}"
threadld.ToString() ) ;
int sum =0; // сумма = О for (int i = start; i < start + 10; i++)
{
Console::WriteLine(i.ToString());
Thread::Sleep(500);
// Поток:: Бездействие sum += i;
// суммируем
}
return sum;
// сумма
}
Функция Main (Главная) определяет два обратных вызова и вызывает их явно с различными начальными целыми числами. Она ждет, пока оба из дескрипторов синхронизации не получат сигнал. Endlnvoke вызывается для обоих и результат выводится на консоль.
Numbers *n = new Numbers;
// новые Числа
Print *pfnl = new Print(n, Numbers::PrintNumbers);
// новая Печать
Print *pfn2 = new Print (n, Numbers::PrintNumbers};
// новая Печать lAsyncResult *arl =
pfnl->Begin!nvoke(0, 0, 0);
lAsyncResult *ar2 =
pfn2->Begin!nvoke(100, 0, 0);
WaitHandle *wh [] = new WaitHandle*[2];
wh[0] = arl->AsyncWaitHandle;
wh[l] = ar2->AsyncWaitHandle;
// удостоверимся, что все сделано перед окончанием
WaitHandle::WaitAll(wh);
int suml = pfnl->End!nvoke(arl);
int sum2 = pfn2->End!nvoke(ar2);
Console::WriteLine(
"Suml = {0} Sum2 = {!}",
suml.ToString(), sum2.ToString());
Вот выдача программы.
MainThread Id: 2 // Идентификатор
PrintNumbers Id: 14 // Идентификатор 0
1
2
3
4
5
6
7
8
9
PnntNumbers Id: 14 // Идентификатор
100
101
102
103
104
105
106
107
108
109
Suml =45 Sum2 =1045

Удаленный доступ


Технология удаленного доступа использует все ключевые концепции прикладной модели .NET (.NET Application Model). Хотя подробное обсуждение удаленного доступа не является целью этой книги, в кратком введении мы представим хороший пример использования метаданных и передачи по ссылке (marshal by reference, MBR) Удаленный доступ представляет собой механизм, который поддерживает исполняемые серверы
В отличие от удаленного доступа в технологии, основанной на модели компонентных объектов Microsoft (COM), в .NET для программирования инфраструктуры требуются минимальные усилия Необходимая инфраструктура программы позволяет программисту выбирать степень гибкости, т.е способность настраивать удаленный доступ для своих собственных приложений
Каркас NET Framework предоставляет два способа устанавливать соединения между двумя прикладными программами на различных компьютерах Рассматриваемые в главе 11 "Web-службы" сетевые службы позволяют компьютерам, на которых не установлена общеязыковая среда времени выполнения CLR, связываться с компьютерами, на которых общеязыковая среда времени выполнения CLR установлена Технология удаленного доступа, обсуждаемая здесь, позволяет создать распределенное приложение при условии, что на компьютерах установлена общеязыковая среда времени выполнения CLR.

Краткий обзор удаленного доступа


Ниже перечислены ключевые части удаленного доступа
• перехват, с помощью которого генерируются сообщения для связи по каналам;
• форматеры (Formatters), служащие для помещения сообщения в байтовый поток, который посылается по каналу Это те же самые форматеры, которые обсуждались ранее в разделе, посвященном сериализации (преобразованию в последовательную форму),
• каналы связи для транспортировки (передачи) сообщений
Перехват
Заместители и заглушки (называемые в NET диспетчерами, организующими программами и планировщиками) преобразовывают обращение к функции на клиентской или серверной стороне в сообщения, которые посылаются по сети Такой процесс называется перехватом, потому что заместители и диспетчеры перехватывают вызов метода для посылки его удаленному адресату. В отличие от модели компонентных объектов Microsoft (COM), общеязыковая среда времени выполнения CLR может генерировать заместители и заглушки с помощью информации, хранимой в метаданных.
Заместитель убирает вызов функции из стекового фрейма вызывающей программы и преобразовывает его в сообщение. Потом сообщение посылается адресату. Диспетчер принимает сообщение и помещает его в стековый фрейм так, чтобы запрос мог быть адресован соответствующему объекту.
Например, предположим, что метод UnregisterCustomer из сборки Customer (Клиент) выполняется в одной прикладной области и вызывается из другой. Нет никакой разницы, если прикладные области находятся в том же самом процессе или на той же самой машине.
Заместитель принимает целочисленный параметр id (идентификатор) из стекового фрейма клиента, который делает вызов и помещает его в сообщение, кодирующее запрос и его параметр. На серверной стороне диспетчер принимает это сообщение и помещает обращение к функции в стек сервера для вызова UnregisterCustomer (int id), a также преобразует этот вызов объекта. Коды клиента и сервера могут даже не знать, что они удалены друг от друга.
Каналы и форматеры
Форматер преобразовывает сообщение в поток байтов. Каркас .NET Framework поставляется с двумя форматерами, двоичным и SOAP (использующим XML-документы и рассматриваемым в главе 11 "Web-службы"). Поток байтов затем посылается по каналу связи.
Каркас .NET Framework поставляется также с двумя каналами, хотя можно создать и свой собственный. Канал HTTP, использующий протокол передачи гипертекстовых файлов HTTP хорош для соединения по Internet или через брандмауэры. Канал TCP использует протокол управления передачей TCP (сокеты) и предназначен для высокоскоростной связи. Таким образом, имеются четыре возможных перестановки форматеров и каналов транспортировки: двоичный по протоколу управления передачей TCP, двоичный по протоколу передачи гипертекстовых файлов HTTP, SOAP по протоколу управления передачей TCP и SOAP по протоколу передачи гипертекстовых файлов HTTP.

Удаленные объекты


Клиентская часть организовывает заместитель, активизируя (вызывая) удаленный объект. Удаленные объекты должны быть производными от MarshalByRefObject, потому что вы работаете с заместителем объектной ссылки, а не непосредственно с самой объектной ссылкой. Это та же концепция, что и ранее при обсуждении понятия контекста, где для обращения к объектам, находящимся в другом контексте, также используется передача по ссылке.
Локальные объекты, передаваемые в качестве параметров методов из одной прикладной области в другую, можно передавать по значению (копировать) или по ссылке.
Чтобы передать объект по значению, он должен быть сериализован (преобразован в последовательную форму). Объект преобразуется в последовательную форму, передается на транспортном уровне, и воссоздается на другой стороне. Это мы уже видели в примере AppDomain (Прикладная область).
Чтобы передать класс по ссылке, он должен быть производным от MarshalByRef Object. Пример Remoting иллюстрирует передачу объекта по ссылке.
Удаленные объекты могут быть активированы как сервером, так и клиентом. Активизированные сервером объекты не создаются до вызова первого метода этого объекта. Они подразделяются на две разновидности, SingleCall и Singleton (Одноэлементное [одноточечное] множество). SingleCall — это объект без состояния, т.е. объект, не меняющий свое состояние в процессе выполнения. Вызов каждого метода приводит к созданию нового объекта. Другая разновидность объектов. Singleton (Одноэлементное [одноточечное] множество), работает иначе. Один и тот же экземпляр Singleton (Одноэлементное [одноточечное] множество) объекта может использоваться для обслуживания нескольких клиентских запросов. Объекты Singleton (Одноэлементное [одноточечное] множество) могут иметь состояние. Объекты Singleton (Одноэлементное [одноточечное] множество) существуют постоянно. Использование объектов SingleCall предпочтительнее, чем объектов Singleton (Одноэлементное [одноточечное] множество), если нужна лучшая масштабируемость, потому что они не сохраняют состояние и при их использовании легче сбалансировать загрузку.
Вызванные клиентом объекты активизируются тогда, когда клиент их запрашивает. Хотя они могут использоваться для нескольких запросов и хранить свое состояние, но не могут хранить информацию о различных вызовах со стороны клиента. Это похоже на вызов CoCreatelnstanceEx в распределенной модели компонентных объектов DCOM.

Активация


Объекты активизируются на стороне клиента одним из трех способов, используя класс Activator (Активатор, Модуль активизации).
• Activator: :GetObject используется для получения ссылки на активизированный сервером объект.
• Activator: :Createlnstance используется для создания активизированного пользователем объекта. Параметры для конструктора можно передать, используя один из перегруженных методов Createlnstance, который принимает массив объектов для передачи их конструктору.
• Синтаксис оператора new (создать) языка C++ может использоваться для создания активизированного сервером или клиентом объекта. Для описания способа применения new (создать) используется конфигурационный файл.

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика