программирование, создание программ, учебник Delphi, учебник по программированию, бейсек, делфи, си, паскаль
 
 
 

 

Пример удаленного объекта

Для нашего примера удаленного доступа мы изолируем наш объект Customers (Клиенты) от сборки Customer (Клиент). В папке примера Remoting находятся два решения. Одно представляет собой клиентскую часть программы, другое — серверную. Сначала нужно скомпоновать серверную часть приложения. При этом также создастся Customer.dll. Необходимо скопировать эту динамически подключаемую библиотеку (DLL) в папку Debug (Отладка) для решений Server (Сервер) и Client (Клиент). Нужно обратить внимание, что сначала запускается программа-сервер, которая после запуска переходит в режим ожидания клиентского запроса. После этого можно выполнить клиентскую часть приложения, которая активизирует объекты, существующие внутри сервера. Подробности программного кода клиента и сервера мы обсудим в нескольких последующих разделах.
Отметим, что нужно было внести два простых изменения в наш объект. Класс Customers (Клиенты) в проекте сервера — удаленный (с возможностью удаленного доступа), — для этого мы сделали его производным от MarshalByRefObject.
public _gc class Customers :
// класс сборщика мусора Клиенты:
public MarshalByRefObject, public ICustomer
CustomerListltem, который будет передаваться по значению, должен быть сделан преобразуемым в последовательную форму.
[Serializable]
// [Преобразуемый в последовательную форму]
public _value struct CustomerListltem
{
public:
int Customerld;
String *FirstName; // Строка
String *LastName; // Строка
String *EmailAddress; // Строка
};

Пример программы, реализующей удаленный доступ
В примере Remoting клиент обращается к активизированному сервером объекту. Сервер— это класс TcpServerChannel, использующий двоичный формат с протоколом управления передачей TCP. Канал будет использовать порт 8085. Сервер регистрирует удаленный тип, имя конечной точки для ссылки на этот объект и тип активизации. Потом сервер переходит в состояние ожидания клиентских запросов.
TcpServerChannel *chan = new TcpServerChannel(8085);
ChannelServices::RegisterChannel(chan); // канал
RemotingConfiguration::RegisterWellKnownServiceType(
_typeof(Customers), // Клиенты
"AcmeCustomer",
WellKnownObjectMode::Singleton); // Единичный предмет
Сервер должен быть запущен прежде, чем клиентская программа обратится к объекту. Клиент устанавливает объект TcpClientChannel и затем присоединяется к нему. . Он определяет требуемый тип объекта и конечную точку, в которой сервер будет ожидать запросы объекта. Если нужно выполнить клиент и сервер на отдельных машинах, необходимо указать имя машины сервера для локального узла в конечной точке. В отличие от прозрачности определения места расположения в модели компонентных объектов Microsoft (СОМ), клиент должен сам определить конкретную конечную точку. Здесь нет переадресации с помощью скрытой записи в системном реестре.
TcpClientChannel *chan = new TcpClientChannel;
ChannelServices::RegisterChannel(chan); // канал
Customers *obj = dynamic_cast<Customers *>( // Клиенты
Activator::GetObject( // Активатор
_typeof(Customers), // Клиенты
"tcp://localhost:8085/AcmeCustomer"));
if (obj == 0) // если (obj == 0)
Console::WriteLine("Could not locate server");
// ("He могу определить местонахождение сервера");
else
{
Потом клиент использует заместитель, чтобы вызвать объект так, как будто это локальный экземпляр.
bool bRet = // логический (булев)
RemotingServices::IsTransparentProxy(obj);
ArrayList *ar;
ar = obj->GetCustomer(-1);
ShowCustomerArray(ar);
Чтобы выполнить программу, нужно сначала запустить сервер с одной консоли, а затем запустить клиент в другом консольном окне.
Вывод зависит от того, какой объект активизирует сервер. Если на сервере активизируется Singleton (Одноэлементное [одноточечное] множество) — объект, который может иметь состояние, то получим поведение, какого можно ожидать в случае недистан-циированного объекта. Когда новый клиент добавлен, его можно обнаружить в списке, если сделать запрос обо всех существующих клиентах. Как и следовало ожидать, начальный активизирующий вызов приводит к тому, что конструктор Customers (Клиенты) вызывается один раз при каждом инициировании работы сервера, независимо от количества запусков клиентской программы.
Object reference a proxy?: True
Client: AppDomain Client.exe Thread 19 Context 0
1 Rocket Squirrel rocky@frosbitefalls.com
2 Bullwinkle Moose moose@wossamotta.edu
1 Rocket Squirrel rocky@frosbitefalls.com
2 Bullwinkle Moose moose@wossamotta.edu
3 Boris Badenough boris@no-goodnicks.com
Результаты весьма отличаются от приведенных выше, если тип активизации — SingleCall, при котором для каждого вызова метода создается новый экземпляр объекта. При этом создаются четыре различных объекта. Первый объект создан при начальном активизирующем запросе. Второй создается при начальном вызове метода GetCustomer. Третий объект создается вследствие вызова RegisterCustomer. И четвертый — в результате повторного вызова метода GetCustomer. Последний созданный объект никогда не увидит нового клиента, потому что состояние не сохраняется. Обратите внимание, что статический член nextCustld класса Customer (Клиент) обрабатывается как статический относительно новых экземпляров объектов класса Customer (Клиент), т.е. именно так, как того и следовало ожидать. Тот же программный код клиента, а результаты различные! Поскольку объект уже активизирован, при повторном выполнении клиентской программы для той же самой инициализации работы сервера, конструктор Customer (Клиент) будет вызван только трижды.
Object reference a proxy?: True
Client: AppDomain Client.exe Thread 19 Context 0
3 Rocket Squirrel rocky@frosbitefalls.com
4 Bullwinkle Moose moose@wossamotta.edu
8 Rocket Squirrel rocky@frosbitefalls.com
9 Bullwinkle Moose moose@wossamotta.edu
Поскольку клиент использует заместитель, объект выполняется в рамках прикладной области сервера, но в потоке, отличном от главного потока сервера. Конструктор объекта не вызывается до первого вызова какого-нибудь метода этого объекта. В обоих случаях
мы получили удаленный список массивов (ArrayList) типов, не проделывая какой-либо специальной работы, кроме указания на то, что тип может быть преобразован в последовательную форму. Именно использование метаданных значительно облегчает работу программиста.

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


Для того чтобы запросить объект определенного типа, клиенту должны быть доступны метаданные о типе. В примере программы удаленного доступа, рассмотренной в этой главе, просто делается ссылка на реальную сборку, в которой хранится объект. Однако во многих приложениях доступ клиента к реальной сборке нежелателен, так как тогда ее можно будет декомпилировать в исходный код с помощью отражения (т.е. восстановить структурную схему и алгоритм работы по исходным текстам). Чтобы получить метаданные, необходимые для клиентской программы, нужно ссылаться только на объект, который содержит информацию об интерфейсе, а не детали реализации.
Один из возможных способов сделать это состоит в том, чтобы создать версию объекта, которая содержит методы без реализации. Этот интерфейсный класс можно потом встроить в сборку, которая предоставляется клиенту. Можно запускать исключение System: :NotSupportedException в методах, чтобы удостовериться в том, что они никогда не будут по ошибке использованы реальным объектом.
Для Web-служб можно использовать утилиту SOAPSUDS, с помощью которой извлекаются метаданные из службы, а затем сгенерировать сборку с требуемыми метаданными. Далее можно скомпилировать заместитель динамически подключаемой библиотеки (DLL), на который должна ссылаться программа-клиент. Концептуально это эквивалентно первому подходу. Сервер, конечно, ссылается на реальную сборку.
В отличие от модели компонентных объектов Microsoft (COM), здесь нет счетчика ссылок, создания и регистрирования отдельных заместителей и заглушек, согласования интерфейсов, беспокойства относительно глобальных идентификаторов и использования системного реестра. Благодаря метаданным для удаленного доступа к объекту необходимо только, чтобы этот объект был производным от MarshalByRef Object.

Конфигурационные файлы удаленного доступа


Конфигурационные файлы используются, чтобы определить, где будет активизирован объект. Клиент затем использует оператор new (создать) для создания объекта. Большое преимущество использования такого подхода заключается в том, что при изменении места расположения (например, унифицированного указателя информационного ресурса (URL) или канала TCP), либо при замене используемого форматера, не нужно будет перепрограммировать клиент.
Многие классы могут быть сконфигурированы клиентом. Файлы конфигурации клиент загружает с помощью метода RemotingConf iguration: : Configure.

Программируемые атрибуты


В главе 5 "Управляемый C++ в .NET Framework" мы ввели концепцию атрибутов, которые уже появлялись в нескольких примерах. В этой главе мы использовали атрибуты Serializable (Преобразуемый в последовательную форму) и Synchronization (Синхронизация), которые предоставляются классами .NET Framework. Каркас .NET Framework делает механизм использования атрибутов полностью расширяемым, позволяя определять собственные произвольные атрибуты, которые могут быть добавлены к метаданным класса. Эти пользовательские метаданные доступны благодаря механизму отражения и могут быть использованы во время выполнения. Чтобы упростить использование самостоятельно определенных атрибутов, можно объявить базовый класс, который будет вызывать отражающий интерфейс прикладного программирования (API) для получения информации из метаданных.
В примере CustomAttribute иллюстрируется использование самостоятельно созданного атрибута InitialDirectory. InitialDirectory указывает начальный текущий каталог, из которого запускается программа. По умолчанию текущим каталогом является тот, в котором содержится решение, и в нашем случае это каталог С:\01\NetCpp\Chap08\CustomAttribute.

Использование самостоятельно созданного атрибута


Перед тем как обсудить реализацию собственного атрибута, рассмотрим, как используется атрибут InitialDirectory. Чтобы указать начальный каталог для класса, мы сделаем класс производным от базового класса DirectoryContext. Тогда мы сможем применить к такому классу атрибут InitialDirectory, который принимает параметр типа String*, определяющий путь к начальному каталогу. Свойство DirectoryPath извлекает путь из метаданных. Если к нашему классу не применен атрибут, этот путь примет значение по умолчанию. Ниже приведен код нашей тестовой программы.
Выполняя этот пример на произвольной системе, можно сменять каталог, указанный атрибутом, на один из существующих на этой конкретной машине.
//AttributeDemo.h
using namespace System;
// использование пространства имен Система;
using namespace System::10;
// использование пространства имен Система::10;
_gc class Normal : public DirectoryContext
Т"
};
[InitialDirectory("С:\\OI\\NetCpp\\Chap08")
] _gc class Special : public DirectoryContext // класс сборщика мусора
Специальный:DirectoryContext
{
};
public _gc class AttributeDemo
// класс сборщика мусора AttributeDemo
{
public:
static void Main() {
Normal *objNormal = new Normal;
Console::WriteLine(
"path = {0}",
objNormal->DirectoryPath);
// путь = ShowDirectoryContents(objNormal->DirectoryPath) ;
Special *objSpecial = new Special;
// новый Специальный Console::WriteLine(
"path = {0}",
objSpecial->DirectoryPath);
// путь = ShowDirectoryContents(objSpecial->DirectoryPath);
} private:
static void ShowDirectoryContents(String *path)
// Строка {
Directorylnfo *dir = new Directorylnfo(path);
// путь
Filelnfo *files[] = dir->GetFiles();
Console::WriteLine("Files:");
// Файлы:
lEnumerator *pEnum = files->GetEnumerator();
// файлы
while (pEnum->MoveNext())
{
Filelnfo *f =
dynamic_cast<File!nfo *>(pEnum->Current);
Console::WriteLine(" {0}", f->Name);
// Имя
}
Directorylnfo *dirs [] = dir->GetDirectories(
};
Console::WriteLine("Directories:");
// Каталоги: pEnum = dirs->GetEnumerator();
while (pEnum->MoveNext()
}
{
Directorylnfo *d =
dynamic_cast<Directory!nfo *>(pEnum->Current);
Console::WriteLine(" {0}", d->Name);
// Имя
}
}
};
Вот выдача:
path = c:\OI\NetCpp\Chap08\CustomAttribute // путь Files: // Файлы
CustomAttribute.vcproj
CustomAttribute.neb
ReadMe.txt
CustomAttribute.cpp
Assemblylnfо.cpp
stdafx.cpp
stdafx.h
CustomAttribute.sin
CustomAttribute.suo
AttributeDemo.h
DirectoryContext.h
DirectoryAttribute.h Directories: // Каталоги
Debug // Отладка path = C:\OI\NetCpp\Chap08 // путь Files: // Файлы Directories: // Каталоги
Reflection // Отражение
Dynamic // Динамический
Filel()
Serialization // Преобразование в последовательную форму
Hotel // Гостиница
ISerialization
Threading
PulseAll
Threadlsolation
AppDomain
Asynch
AsynchThreading
CustomAttribute
MarshalByReference
Remoting

Определение класса атрибута


Чтобы создать пользовательский атрибут, необходимо определить класс атрибута, производный от базового класса Attribute (Атрибут). В соответствии с соглашением, нужно дать классу имя, заканчивающееся на "Attribute" ("Атрибут"). Имя класса без суффикса "Attribute" ("Атрибут") будет названием пользовательского атрибута. В нашем примере имя класса — InitialDirectoryAttribute, поэтому название атрибута— Initial-Directory.
Можно реализовать один или несколько конструкторов для класса атрибута. Конструкторы определяют, как передать позиционные параметры для атрибута (предоставив список параметров, разделенных запятыми). Возможно также предусмотреть "поименованные параметры" для пользовательского атрибута, чтобы при передаче информации через параметр можно было использовать синтаксис имя=значение.
Можно также предусмотреть свойства для чтения информации, передаваемой через параметр. В нашем примере есть свойство Path (Путь), которое инициализируется в конструкторе.
//DirectoryAttribute.h
using namespace System;
// использование пространства имен Система;
public _gc class InitialDirectoryAttribute :
// класс сборщика мусора InitialDirectoryAttribute:
public Attribute
// общедоступный Атрибут
{
private:
// частный
String *path;
// Строка public:
InitialDirectoryAttribute(String *path)
// Строка
{
this->path = path;
// путь
}'
_property String *get_Path()
// Строка свойства
{
return path;
// путь
}
};

Определение базового класса


Последний шаг при работе с самостоятельно создаваемыми атрибутами состоит в том, чтобы предусмотреть способ извлечения пользовательской информации об атрибуте из метаданных при помощи классов отражения. Можно получить Туре (Тип) любого объекта, вызывая метод GetType, который предоставляется корневым классом Object (Объект). Метод GetCustomAttributes этого класса позволяет прочитать пользовательскую информацию об атрибуте.
Чтобы упростить создание клиентской программы, часто полезно создать базовый класс, который будет проделывать работу по чтению пользовательской информации об атрибутах30. Мы создадим базовый класс DirectoryContext, который будет использоваться классом, желающим воспользоваться преимуществом атрибута Initial-Directory. Этот базовый класс предоставляет свойство DirectoryPath, чтобы возвратить информацию о пути, хранящуюся в метаданных. Вот листинг базового класса:
//DirectoryContext.h
using namespace System;
// использование пространства имен Система;
using namespace System::Reflection;
// использование пространства имен Система::Отражение;
using namespace System::10;
// использование пространства имен Система::10;
using namespace System:Collections;
использование пространства имен Система::Коллекции;
_gc class DirectoryContext
// класс сборщика мусора DirectoryContext
{
public:
_property String *get_DirectoryPath()
// Строка свойства
{
Type *t = this->GetType();
lEnumerator *pEnum =
t->GetCustomAttributes(true)->GetEnumerator();
while (pEnum->MoveNext())
{
Attribute *a =
dynamic_cast<Attribute *>(pEnum->Current);
// Атрибут InitialDirectoryAttribute *da =
dynamic_cast<InitialDirectoryAttribute *>(a);
if (da != 0)
// если (da ! = 0)
{
return da->Path;
// Путь
}
}
return Directory::GetCurrentDirectory();
// Каталог
}
};
Создав базовый класс, можно столкнуться с трудностями, если допускается только единичное наследование реализации. Если класс должен быть производным от другого класса, например ContextBoundObject, то ваш базовый класс должен быть производным от этого класса
Нужно импортировать пространство имен System:: Reflection (Система-Отражение). GetType возвращает текущий объект Туре (Тип), и тогда можно использовать метод GetCustomAttributes, чтобы получить коллекцию объектов Attribute (Атрибут) из метаданных. Так как эта коллекция неоднородна, поскольку состоит из различных типов, используется оператор dynamic_cast, чтобы проверить, принадлежит ли данный элемент коллекции к типу InitialDirectoryAttribute. Если такой элемент найдется, возвращается свойство Path (Путь). В противном случае возвращается заданный по умолчанию текущий каталог, который можно получить из GetCurrentDirectory.

Сборка мусора


Распределенная управляемая память автоматически возвращается системе с помощью алгоритма сборки мусора. Общеязыковая среда времени выполнения CLR отслеживает использование памяти, которая выделяется в управляемой ею динамически распределяемой области памяти, и любой участок памяти, на который больше ничто не ссылается, отмечается как "мусор". Когда памяти не хватает, общеязыковая среда времени выполнения CLR просматривает свои структуры данных, с помощью которых она отслеживает использование памяти, и возвращает всю память, помеченную как мусор. Таким образом, программист освобождается от ответственности за освобождение памяти.
Хотя сборка мусора предотвращает утечки памяти в управляемой динамически распределяемой области памяти, она не помогает освобождать другие типы ресурсов. Эти ресурсы включают в себя открытые файлы или соединения с базами данных, а также соединения с сервером, которые должны быть разъединены. Программист должен написать код для явной очистки этих ресурсов. Это можно сделать в деструкторе класса или в специализированном методе для очистки. Общеязыковая среда времени выполнения CLR вызовет этот деструктор для освобождения объекта.
При сборке мусора очень важна эффективность. С автоматической сборкой мусора связаны определенные накладные расходы. Однако общеязыковая среда времени выполнения CLR имеет эффективный алгоритм сборки мусора, учитывающий поколения объектов, участвующих в сборке мусора.

Уничтожение объектов


Исключая ситуацию явного использования оператора delete (уничтожить), для управляемого объекта время разрушения недетерминировано. Деструктор для конкретного объекта, на который нет ссылок, может вызваться в любое время, когда выполняется процесс сборки мусора. Порядок вызовов деструкторов для различных объектов непредсказуем. Более того, при исключительных обстоятельствах деструктор может не вызваться совсем (например, поток войдет в бесконечный цикл или процесс завершится аварийно и тогда во время выполнения не будет возможности провести очистку). К тому же, если оператор delete (уничтожить) не используется явно, то поток, из которого будет вызван деструктор, не определен.
В примере ExplicitDelete демонстрируется, что вызов деструктора является синхронным, и поэтому детерминированным, если явно удалить управляемый указатель. Следующий код демонстрирует создание двух объектов. Первый завершается пассивно путем обнуления указателя на него. Сборщик мусора асинхронно вызывает деструктор в своем собственном потоке. Второй объект разрушается явно с помощью оператора delete (уничтожить) и деструктор вызывается синхронно. Программа с помощью хэш-кодов отображает подробности того, что случается с каждым объектом, и в каком потоке это случается. Из вывода можно увидеть, что в случае пассивно завершаемого объекта деструктор выполняется в потоке, отличном от того, в котором выполняется функция Main (Главная). И напротив, в случае явного уничтожения объекта деструктор выполняется в том же самом потоке, что и метод Main (Главный).
//ExplicitDelete.h
using namespace System::Threading;
// использование пространства имен
// Система::Организация поточной обработки;
public _gc class SomeClass
// класс сборщика мусора SomeClass
{
public:
-SomeClass()
{
Console::Write ( // Запись
"Destructor running in thread: {0}, ",
// "Деструктор, выполняющийся в потоке: (0), ",
_box(Thread::CurrentThread->GetHashCode()));
// Поток Console::WriteLine(
"Destroying object: {0}",
// "Уничтожение объекта: {0} ",
_box(this->GetHashCode()));
}
};
public _gc class ExplicitDelete
// класс сборщика мусора ExplicitDelete
{
public:
static void Main()
{
Console::WriteLine(
"Main thread: {0}",
// "Основной поток: {0} ",
_box(Thread::CurrentThread->GetHashCode()));
// Поток
SomeClass *sc = new SomeClass;
Console::WriteLine(
"Main thread creating object: {0}",
// "Основной поток, создающий объект: {0} ",
_box(sc->GetHashCode()));
Console::WriteLine(
"Nulling pointer to object: {0}",
// "Обнуление указателя на объект: {0} ",
_box(sc->GetHashCode()));
sc = 0;
GC::Collect (); // СБОРЩИК МУСОРА:: Собрать();
GC::WaitForPendingFinalizers(); // СБОРЩИК МУСОРА
sc = new SomeClass;
Console::WriteLine(
"Main thread creating object: {0}",
// "Основной поток, создавший объект: {0} ",
_box(sc->GetHashCode()));
Console::WriteLine(
"Deleting pointer to object: {0}",
// "Удаление указателя на объект: {0} ",
_box(sc->GetHashCode()));
delete sc; // удалить
Console::WriteLine("All done."); // Все сделано
}
};
Ниже приведена выдача.
Main thread: 2
Main thread creating object: 5
Nulling pointer to object: 5
Destructor running in thread: 6,
Destroying object: 5
Main thread creating object: 7
Deleting pointer to object: 7
Destructor running in thread: 2,
Destroying object: 7
All done.
Перевод такой:
Основной поток: 2
Основной поток, создавший объект: 5
Обнуление указателя на объект: 5
Деструктор, выполняющийся в потоке: 6,
Уничтожаемый объект: 5
Основной поток, создавший объект: 7
Удаление указателя на объект: 7
Деструктор, выполняющийся в потоке: 2,
Уничтожаемый объект: 7
Все сделано.
Чтобы избежать лишних накладных расходов, не стоит определять деструктор для класса, если на то нет серьезных причин. В случае, если деструктор все же реализуется, нужно, вероятно, в классе предоставить альтернативный, детерминированный механизм для выполнения необходимой очистки. Каркас .NET Framework рекомендует использовать шаблон проектирования Dispose (Освободить ранее выделенную область памяти) для детерминированной очистки, которую мы сейчас и рассмотрим.

Неуправляемые ресурсы и освобождение ранее выделенной области памяти


Предположим, что некоторый объект, с помощью которого был открыт файл, программе больше не нужен и помечен для сборки мусора. В конечном счете будет вызван деструктор объекта, при выполнении которого файл может быть закрыт. Но, как мы уже обсуждали, сборка мусора — процесс недетерминированный (во времени, во всяком случае), и файл может оставаться открытым неопределенно долго. Более эффективным было бы иметь в клиентской программе детерминированный механизм освобождения ресурсов объекта, который стал ненужным. Каркас .NET Framework рекомендует для этой цели использовать интерфейс IDisposable.
public _gc _interface IDisposable
// сборщик мусора - интерфейс IDisposable
{
void Dispose();
};
В этом шаблоне проектирования определяется, что клиентская программа должна вызывать Dispose (Освободить ранее выделенную область памяти) для объекта, когда исчезает необходимость в этом объекте. Метод Dispose (Освободить ранее выделенную область памяти) реализуется так, что класс выполняет все необходимые действия по очистке. Для полной гарантии, в классе нужно реализовать и деструктор— на случай, если метод Dispose (Освободить ранее выделенную область памяти) никогда не будет вызван, возможно, из-за появления исключения33. Так как и метод Dispose (Освободить ранее выделенную область памяти), и деструктор производят очистку, код, освобождающий ресурсы, может быть помещен в Dispose (Освободить ранее выделенную область памяти), а в деструкторе можно просто вызывать Dispose (Освободить ранее выделенную область памяти). Метод Dispose (Освободить ранее выделенную область памяти) разработан так, что клиентская программа может вызывать его при завершении работы с объектом или когда будет известно, что нет никакой опасности в освобождении ресурсов, связанных с объектом.
Отметим, что недопустимо вызывать деструктор объекта после вызова метода Dispose (Освободить ранее выделенную область памяти), потому что это приведет к повторной очистке. Объект может быть удален из очереди сборки мусора с помощью GC: : SuppressFinalize. Кроме того, полезно реализовать в классе булев флажок, назвав его, например disposeCalled. Если Dispose (Освободить ранее выделенную область памяти) вызывается дважды, проверка этого флажка предотвратит повторное выполнение очистки.
Метод Dispose (Освободить ранее выделенную область памяти) должен также вызвать метод Dispose (Освободить ранее выделенную область памяти) базового класса с целью удостовериться, что все его ресурсы тоже освобождаются. Причем и этот метод должен быть написан так, чтобы не возникала исключительная ситуация при его вызове, если ресурсы уже были освобождены.
Поскольку завершение представляет собой дорогой процесс, любой объект, который больше не будет использовать ресурсы, должен вызвать статический метод GC:: SupressFinalize, передавая ему указатель this. При наличии в коде блока try/finally можно разместить вызов метода Dispose (Освободить ранее выделенную область памяти) объекта в блоке finally (наконец), чтобы удостовериться в том, что ресурсы будут освобождены.
Пример программы DisposeDemo иллюстрирует шаблон для освобождения ресурсов. Класс SimpleLog с помощью класса StreamWriter реализует запись (информации) в файл.
//SimpleLog.h
using namespace System;
// использование пространства имен Система;
using namespace System::10;
// использование пространства имен Система::10;
public _gc class SimpleLog :
public IDisposable
// класс сборщика мусора
SimpleLog: IDisposable
{
private: // частный
StreamWriter *writer; String *name;
// Строка bool disposeCalled;
// логический (булев) флажок disposeCalled public:
SimpleLog(String *fileName) : disposeCalled(false)
// (Строка *fileName): (ложь)
{
name = fileName; // имя файла
writer = new StreamWriter(fileName, false);
// устройство записи = новый StreamWriter (имя
// файла, ложь);
writer->AutoFlush = true;
// устройство записи-> Автосброс = истина;
Console::WriteLine(
String::Format("logfile {0} created", name));
// Строка::Формат ("системный журнал (0}
// создан", имя));
}
void WriteLine(String *str) // Строка
{
writer->WriteLine(str); // запись
Console::WriteLine(str);
}
void Dispose ()
{
if(disposeCalled)
// если
(disposeCalled) - если все уже сделано
return;
writer->Close ();
GC::SuppressFinalize (this);
// СБОРЩИК МУСОРА Console::WriteLine(
String::Format("logfile {0} disposed", name));
// Строка::Формат ("системный журнал {0}
// закрыт ", имя));
disposeCalled = true;
// истина - все уже сделано
}
-SimpleLog()
{
Console::WriteLine(
String::Format("logfile {0} finalized", name));
// Строка::Формат ("системный журнал (0)
// завершен", имя));
Dispose();
}
};
Класс SimpleLog поддерживает интерфейс IDisposable и таким образом реализует метод Dispose (Освободить ранее выделенную область памяти). Код, освобождающий ресурсы, просто удаляет объект streamWriter. Чтобы удостовериться в том, что для удаленного объекта также не будет вызван метод завершения, вызывается GC: : SuppressFinalize. Завершающий работу объекта деструктор просто делегирует свои функции методу Dispose (Освободить ранее выделенную область памяти). Для контроля над продолжительностью жизни объекта на консоль выводится сообщение из конструктора, из метода Dispose (Освободить ранее выделенную область памяти) и из деструктора.
Вот код тестовой программы:
//DisposeDemo.h
using namespace System;
// использование пространства имен Система;
using namespace System::Threading;
// использование пространства имен
// Система::Организация поточной обработки;
public _gc class DisposeDemo
// класс сборщика мусора DisposeDemo
{ public:
static void Main()
{
SimpleLog *log = new SimpleLog("logl.txt");
log->WriteLine("First line");
// файл регистрации-> WriteLine ("Первая строка");
Pause(); // Пауза
log->Dispose(); // Первое завершение файла регистрации
log->Dispose(); // файл регистрации - испытание -второй
// вызов Dispose
log = new SimpleLog("Iog2.txt");
log->WriteLine("Second line");
// файл регистрации-> WriteLine ("Вторая строка");
Pause (); // Пауза log = new SimpleLog(
"Iog3.txt"); // предыдущий (2-ой) файл
// регистрации освобожден
log->WriteLine("Third line");
// файл регистрации-> WriteLine ("Третья строка");
Pause (); // Пауза
log =0; // последний файл регистрации освобожден
GC::Collect();
// СБОРЩИК МУСОРА:: Собрать ( );
Thread::Sleep(100);
// Поток:: Бездействие (100);
}
private: // частный
static void Pause)) // Пауза
{
Console::Write("Press enter to continue");
// Запись:: ("Нажмите ввод для продолжения");
String *str = Console::ReadLine(); // Строка
}
};
Указателю log (файл регистрации) на объект SimpleLog по очереди присваиваются три различных указателя на экземпляр объекта. Первый раз ранее выделенная область памяти освобождается должным образом. Второй раз указателю присваивается указатель на третий объект. Это происходит до освобождения ранее выделенной области памяти для второго объекта. В результате второй объект становится мусором. Используя метод Pause (Пауза) для приостановки выполнения этого консольного приложения, мы можем исследовать состояние файлов logl. txt, Iog2 . txt и Iog3. txt в разные моменты выполнения программы.
Выполнение программы приводит к следующему выводу:
logfile logl.txt created First line
Press enter to continue logfile logl.txt
disposed logfile Iog2.txt created Second line
Press enter to continue logfile Iog3.txt created Third line
Press enter to continue logfile Iog3.txt
finalized logfile Iog3.txt
disposed logfile Iog2.txt
finalized logfile Iog2.txt disposed
Перевод такой:
системный журнал logl.txt создан
Первая строка
Нажмите ввод для продолжения
системный журнал logl.txt завершен
системный журнал Iog2.txt создан
Вторая строка
Нажмите ввод для продолжения
системный журнал Iog3.txt создан
Третья строка
Нажмите ввод для продолжения
системный журнал Iog3.txt завершен
системный журнал Iog3.txt освобожден
системный журнал Iog2.txt завершен
системный журнал Iog2.txt освобожден
После первой паузы файл logl. txt уже создан, и мы можем просмотреть его содержимое с помощью стандартной системной программы Блокнот (Notepad). Если попробовать удалить файл, получим нарушение процедуры совместного использования (общих ресурсов), т.е. нарушение условий коллективного доступа (ошибка совместного доступа, т.к. файл уже открыт другим приложением), как показано на рис. 8.2.
После второй паузы ранее выделенная для logl. txt область памяти будет освобождена и его можно будет удалить. Файл Iog2.txt к этому моменту уже был создан и открыт. А до третьей паузы будет создан Iog3.txt. Но объектной ссылке на 1од2 . txt было присвоено новое значение, поэтому теперь клиентская программа не может освободить область памяти, ранее выделенную для второго объекта. Если бы Dispose (Освободить ранее выделенную область памяти) был единственным механизмом для удаления второго объекта, нам бы не повезло. К счастью, в классе SimpleObject реализован деструктор, так что при следующей сборке мусора удастся избавиться от второго объекта должным образом. Можно увидеть результат завершения, выполнив программу до конца. Второй объект действительно завершается и ранее выделенная для него область памяти освобождается. Фактически при завершении работы прикладной области деструкторы вызываются для всех не уничтоженных объектов, даже для объектов, которые все еще являются доступными.
В нашем коде мы явно делаем третий объект недоступным, присваивая указателю пустой указатель (log = null), и принудительно устраиваем сборку мусора, вызывая GC: : Collect (СБОРЩИК МУСОРА::Собрать). Потом приложение на некоторое время переходит в режим . ожидания, чтобы сборщик мусора выполнил свою работу до конца перед завершением прикладной области. Наша тестовая программа несколько искусственна, так как сборка мусора недетерминирована. Сборщик мусора вызывается автоматически при выходе из программы и завершении работы прикладной области. Однако при этом системные объекты, такие как Console (Консоль), также закрываются. Так как нельзя полагаться на порядок завершения, можно получить исключение при вызове WriteLine внутри деструктора. Явный вызов GC: :Collect (СБОРЩИК МУСОРА::Собрать) вызывает принудительную сборку мусора в то время, когда системные объекты все еще открыты. Если мы опустим последние три строки из метода Мат (Главный), то можем получить идентичный вывод, но можем также получить и исключение.
Дополнительное имя для Dispose (Освободить ранее выделенную область памяти)
Стандартное имя метода, производящего очистку— Dispose (Освободить ранее выделенную область памяти). Соглашение состоит в том, что, как только ранее выделенная для объекта область памяти освобождается, такой объект завершает свою работу. Однако в некоторых случаях, например для файла, тот же самый экземпляр объекта мог бы использоваться повторно. Файл может быть открыт, закрыт, а затем снова открыт. При этом стандартное соглашение об именовании гласит, что метод для очистки должен называться Close (Закрыть). В других случаях может использоваться другое подходящее имя.
Чтобы вполне естественно, если бы в нашем классе SimpleLog имелся метод Open (Открыть). В этом случае метод для очистки логично было бы назвать Close (Закрыть). Простоты ради, мы не реализовали метод Open (Открыть), и поэтому придерживались имени Dispose (Освободить ранее выделенную область памяти).

Поколения


Чтобы оптимизировать технологию, каждому объекту, находящемуся в управляемой динамически распределяемой области памяти, назначается поколение. Так, новому объекту назначается поколение 0, и такой объект рассматривается в качестве главного кандидата для сборки мусора. Ранее созданные объекты назначаются поколению 1. Поскольку такие объекты уже просуществовали некоторое время, есть вероятность, что их время жизни будет более продолжительным, чем у объектов поколения 0. Еще более старым объектам назначается поколение 2. Считается, что вероятность пережить сборку мусора у них еще больше. Максимальный номер поколения в текущей реализации .NET равен 2. Это число может быть взято из свойства GC : : MaxGeneration.
Обычно сборщик мусора "выметает" только поколение 0. Именно в этом поколении находятся наиболее вероятные кандидаты на освобождение памяти. Все объекты поколения 0, пережившие сборку мусора, переходят в поколение 1. Если освобожденной памяти недостаточно, будут "выметены" объекты поколения 1, а выжившие будут продвинуты в поколение 2. Ну а затем, в случае необходимости, будут "выметены" и объекты поколения 2 и так далее вплоть до MaxGeneration — максимального количества поколений.

Завершение и раскручивание стека


Как было упомянуто ранее, одно из достоинств механизма обработки исключений заключается в том, что, поскольку при обработке исключения происходит раскрутка стека вызовов, локальные объекты выходят из области видимости и таким образом помечаются для завершения. Простая иллюстрация этого имеется в программе FinalizeStackUnwind. В этой программе используется рассмотренный ранее класс SimpleLog, в котором реализуется завершение.
//FinalizeStackUnwind.h
using namespace System;
// использование пространства имен Система;
using namespace System::Threading;
// использование пространства имен
// Система::Организация поточной обработки;
public _gc class FinalizeStackUnwind
// класс сборщика мусора FinalizeStackUnwind
{
public:
static void Main()
{
try
{
SomeMethod(); }
catch(Exception *e)
// Исключение
{
Console::WriteLine(e->Message);
// Сообщение
}
GC::Collect( ) ;
// СБОРЩИК МУСОРА:: Собрать()
Thread::Sleep(100); // Поток:: Бездействие
}
private: // частный
static void SomeMethod ()
{
// локальная переменная
SimpleLog *alpha = new SimpleLog("alpha.txt");
// вызвать исключение
throw new Exception("error!!");
// новое Исключение ("ошибка!!");
}
};
SomeMethod выделяет место для локальной переменной-указателя alpha (алфавитный) типа SimpleLog*. Перед нормальным завершением метода, из него вызывается исключение. Механизм раскручивания стека при обработке особых ситуаций видит, что переменная-указатель alpha (алфавитный) больше не доступна, и помечает ее для сборки мусора. Вызов GC: :Collect (СБОРЩИК МУСОРА::Собрать) приводит в действие сборку мусора, и мы видим в выдаче программы, что завершение действительно выполнено.
logfile alpha.txt created
error!!
logfile alpha.txt finalized
logfile alpha.txt disposed
Перевод такой:
системный журнал alpha.txt создан
ошибка!!
системный журнал alpha.txt завершен
системный журнал alpha.txt освобожден

Управление сборкой мусора с помощью класса сборщика мусора GC
Обычно лучше всего не вмешиваться в работу сборщика мусора, — пусть он выполняет свою работу незаметно для вас. Иногда, однако, программе может быть выгодно вмешаться в его работу. Пространство имен System (Система) содержит класс GC (СБОРЩИК МУСОРА), который дает возможность программе изменить поведение сборщика мусора. Мы рассмотрим несколько важных методов класса GC (СБОРЩИК МУСОРА).
SuppressFinalize
Данный метод производит запрос к системе, чтобы та не выполняла операцию завершения, т.е. не вызвала деструктор указанного объекта. Как мы видели раньше, нужно вызвать этот метод в собственной реализации метода Dispose (Освободить ранее выделенную область памяти), чтобы предотвратить выполнение операции завершения объекта, если ранее выделенная для него область памяти уже освобождена.
Collect (Собрать)
Можно принудительно выполнить сборку мусора, вызвав метод Collect (Собрать). Необязательный параметр позволяет указать, какие поколения должны принять участие в сборке мусора. Этот метод нужно использовать рационально, так как общеязыковая среда времени выполнения CLR обычно имеет лучшую информацию относительно текущего состояния памяти. Данный метод можно использовать тогда, когда программа только что освободила ряд больших объектов, и нужно иметь всю эту свободную память немедленно. Другой пример использования этого метода был предоставлен в предыдущем разделе, где вызов Collect (Собрать) принудительно вызывает сборку тогда, когда системные объекты все еще доступны.
MaxCeneration
Это свойство возвращает максимальное поддерживаемое общеязыковой средой времени выполнения CLR число поколений.
CetCeneration
Данный метод возвращает текущий номер поколения, которое назначено объекту.
CetTotalMemory
Указанный метод возвращает объем распределенной в настоящее время памяти в байтах (а не объем свободной доступной памяти и не общий объем динамически распределяемой области памяти). Передаваемый параметр позволяет определять, должна ли система выполнить сборку мусора перед возвратом из метода. Если сборка мусора не производится, возвращаемое число байтов вероятно больше, чем фактический объем памяти, используемой объектами.

Программа-пример


Программа GarbageCollection (Сборка мусора) иллюстрирует использование рассмотренных выше методов класса GC (СБОРЩИК МУСОРА). Пример несколько искусственен. С его помощью просто иллюстрируется продолжительность жизни объекта и эффект использования различных методов класса GC (СБОРЩИК МУСОРА). Объекты, для которых выделяется память, принадлежат классу Member (Элемент). Этот класс имеет свойство типа String (Строка) под названием Name (Имя). Операторы вывода использованы в конструкторе, деструкторе и методе Dispose (Освободить ранее выделенную область памяти). Класс Committee (Комитет) поддерживает список массивов, состоящий из экземпляров класса Member (Элемент). Метод RemoveMember просто удаляет элемент из списка массивов. Метод DisposeMember также вызывает метод Dispose (Освободить ранее выделенную область памяти) для вычеркиваемого из списка элемента. Метод ShowGenerations отображает номер поколения каждого объекта класса Member (Элемент). Испытательная программа GarbageCollection.h использует эти классы; она показывает результаты различных размещений (распределений) и освобождений памяти при использовании методов класса GC (СБОРЩИК МУСОРА). Код и вывод должны быть весьма просты для понимания.
Вся память распределяется локально в методе DemonstrateGenerations. После того, как указанный метод завершит свою работу, и его локальная память стает недоступной, мы явно вызываем GC: :Collect (СБОРЩИК МУСОРА: :Собрать). Это приводит к вызову соответствующих деструкторов, прежде чем вся прикладная область прекратит свое существование. Таким образом мы избегаем возможного случайного исключения в закрывающемся потоке, когда метод WriteLine вызывается в методе завершения. Этот самый прием мы уже видели в более ранних примерах.

Резюме


Данная глава представила модель приложений .NET. С помощью метаданных и отражения, каркас может получить всю необходимую информацию о программе, чтобы предоставить множество служб, которые ранее программистам приходилось реализовывать самостоятельно. С другой стороны, мы видели, что каркас структурирован так, чтобы можно было использовать свои собственные объекты и реализации там, где это необходимо.
Типовая безопасность дает возможность прикладным областям обеспечить экономичную и эффективную изоляцию приложений. Контексты, заместители и перехват позволяют во время выполнения прозрачно предоставлять услуги тем частям приложения, которые их требуют.
Еще один аспект модели приложений .NET — распространяющееся использование атрибутов, которые могут быть просто добавлены к исходному коду и сохранены вместе с метаданными. Мы видели примеры использования атрибутов для сериализации и синхронизации. Научились также реализовывать и использовать самостоятельно определяемые атрибуты.
.NET упрощает программирование управления памятью с помощью эффективного, автоматического средства сборки мусора, которое учитывает поколения объектов. Процесс завершения объекта недетерминирован, но можно реализовать детерминированный процесс очистки с помощью шаблона освобождения памяти dispose (Освободить ранее выделенную область памяти) или явного использования оператора delete (уничтожить).

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