c# desktop. Занятие 15

25
Темы лекции: Многопоточность в C#. Практическое задание: Многопоточность в C#. Тренер: Игорь Шкулипа, к.т.н. Платформа .Net и язык программирования C#. Занятие 15

Upload: igor-shkulipa

Post on 21-Mar-2017

76 views

Category:

Education


3 download

TRANSCRIPT

Page 1: C# Desktop. Занятие 15

Темы лекции: Многопоточность в C#.

Практическое задание: Многопоточность в C#.

Тренер: Игорь Шкулипа, к.т.н.

Платформа .Net и язык программирования C#.

Занятие 15

Page 2: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 2

Object Pool

Применение паттерна Object Pool может значительно повыситьпроизводительность системы; его использование наиболееэффективно в ситуациях, когда создание экземпляровнекоторого класса требует больших затрат, объекты в системесоздаются часто, но число создаваемых объектов в единицувремени ограничено.

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

Page 3: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 3

Реализация Object Pool на основе Singleton. Классы объектов

abstract class IObject {

protected string Text;

public virtual void Print() {

Console.WriteLine("The Object is: {0}", Text);

}

}

class Object1 : IObject {

public Object1() {

Text = "Object 1";

}

}

class Object2 : IObject {

public Object2() {

Text = "Object 2";

}

}

class Object3 : IObject {

public Object3() {

Text = "Object 3";

}

}

class Object4 : IObject {

public Object4() {

Text = "Object 4";

}

}

Page 4: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 4

Object Pool

class ObjectPool {

public static ObjectPool GetInstance(int size) {

if (instance == null) instance =

new ObjectPool(size);

return instance;

}

public IObject GetObject() {

for (int i = 0; i < poolSize; i++) {

if (!busyObjects[i]) {

busyObjects[i] = true;

return objectPool[i];

}

}

return null;

}

public void ReleaseObject(IObject obj) {

for (int i = 0; i < poolSize; i++) {

if (objectPool[i] == obj) {

busyObjects[i] = false;

}

}

}...

Page 5: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 5

Object Poolprivate ObjectPool(int size) {

poolSize = size; objectPool = new IObject[poolSize];

busyObjects = new bool[poolSize]; Random rand = new Random(1000);

for (int i = 0; i < poolSize; i++) {

int iObjNumber = rand.Next()%4;

switch (iObjNumber) {

case 0: objectPool[i] = new Object1(); busyObjects[i] = false;

break;

case 1: objectPool[i] = new Object2(); busyObjects[i] = false;

break;

case 2: objectPool[i] = new Object3(); busyObjects[i] = false;

break;

case 3: objectPool[i] = new Object4(); busyObjects[i] = false;

break;

}

busyObjects[i] = false;

}

}

private IObject[] objectPool; private int poolSize;

private bool[] busyObjects; private static ObjectPool instance;

}

Page 6: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 6

Использование Object Poolclass Program {

static void Main(string[] args) {

ObjectPool op = ObjectPool.GetInstance(5);

IObject object1 = op.GetObject();

if (object1 != null) object1.Print(); else Console.WriteLine("The Object is: NULL");

IObject object2 = op.GetObject();

if (object2 != null) object2.Print(); else Console.WriteLine("The Object is: NULL");

IObject object3 = op.GetObject();

if (object3 != null) object3.Print(); else Console.WriteLine("The Object is: NULL");

IObject object4 = op.GetObject();

if (object4 != null) object4.Print(); else Console.WriteLine("The Object is: NULL");

IObject object5 = op.GetObject();

if (object5 != null) object5.Print(); else Console.WriteLine("The Object is: NULL");

IObject object6 = op.GetObject();

if (object6 != null) object6.Print(); else Console.WriteLine("The Object is: NULL");

IObject object7 = op.GetObject();

if (object7 != null) object7.Print(); else Console.WriteLine("The Object is: NULL");

op.ReleaseObject(object2);

IObject object8 = op.GetObject();

if (object8 != null) object8.Print(); else Console.WriteLine("The Object is: NULL");

Console.ReadKey();

}

}

Page 7: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 7

Результат

The Object is: Object 2

The Object is: Object 4

The Object is: Object 3

The Object is: Object 1

The Object is: Object 2

The Object is: NULL

The Object is: NULL

The Object is: Object 4

Page 8: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 8

Домены приложений .NET

В .NET исполняемые файлы не обслуживаются прямо внутри процесса Windows, какэто происходит в случае традиционных неуправляемых приложений. Вместоэтого они обслуживаются в отдельном логическом разделе внутри процесса,который называется доменом приложения (Application Domain — AppDomain).В единственном процессе может содержаться несколько доменов приложений,каждый из которых обслуживает свой исполняемый файл .NET. Такоедополнительное подразделение традиционного процесса Windows предоставляетряд преимуществ:

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

• Домены приложений являются гораздо менее дорогостоящими в планепотребления вычислительных ресурсов и памяти по сравнению сполноценными процессами. Благодаря этому CLR-среде удается загружать ивыгружать домены приложений намного быстрее, чем формальные процессы, итем самым значительно улучшать масштабируемость серверных приложений.

• Домены приложений обеспечивают более глубокий уровень изоляции дляобслуживания загружаемого приложения. В случае выхода из строя какого-тоодного домена приложения внутри процесса, остальные домены приложений всеравно остаются работоспособными.

Page 9: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 9

Класс System.AppDomain

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

• CreateDomain() - статический метод позволяет создавать новый доменприложения в текущем процессе

• CreateInstance() - метод позволяет создавать экземпляр типа из внешнейсборки после загрузки соответствующей сборки в вызывающий доменприложения

• ExecuteAssembly() - метод позволяет запускать сборку *.ехе внутри доменаприложения за счет предоставления имени ее файла

• GetAssemblies() - метод позволяет узнать, какие сборки .NET были загружены вданный домен приложения

• GetCurrentThreadId() - статический метод возвращает идентификатор потока,который является активным в текущем домене приложения

• Load() - метод применяется для динамической загрузки сборки в текущий доменприложения

• Unload() - статический метод позволяет выгрузить определенный доменприложения из конкретного процесса

Page 10: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 10

System.AppDomain свойства

• BaseDirectory - позволяет извлечь путь к каталогу, которыйпреобразователь адресов использует для поиска сборок

• CurrentDomain - представляет собой статическое свойство ипозволяет узнать домен приложения, используемый длявыполняющегося в текущий момент потока

• FriendlyName - позволяет получить дружественное имя текущегодомена приложения

• MonitoringlsEnabled - позволяет получить или установить значение,указывающее, должна ли работать функция мониторинга заиспользованием ресурсов ЦП и памяти для текущего процесса. Послевключения функции мониторинга для процесса отключить ее нельзя

• SetupInformation - позволяет извлечь детали конфигурацииопределенного домена приложения, которые предоставляются в видеобъекта AppDomainSetup

Page 11: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 11

System.AppDomain события

• AssemblyLoad - возникает при загрузке сборки в память

• AssemblyResolve - возникает, когда преобразователю адресов сборокне удается обнаружить место расположения требуемой сборки

• DomainUnload - возникает перед началом выгрузки доменаприложения из обслуживающего процесса

• FirstChanceException - позволяет получать уведомление о том, что вдомене приложения было сгенерировано какое-то исключение, передначалом выполнения CLR-средой поиска подходящего оператора catch

• ProcessExit - возникает в используемом по умолчанию доменеприложения тогда, когда его родительский процесс завершает работу

• UnhandledException - возникает при отсутствии обработчика,способного перехватить данное исключение

Page 12: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 12

Основы многопоточности

Различают две разновидности многозадачности: на основе процессов и на основепотоков.

Процесс отвечает за управление ресурсами, к числу которых относитсявиртуальная память и дескрипторы Windows, и содержит как минимум одинпоток. Наличие хотя бы одного потока является обязательным для выполнениялюбой программы. Поэтому многозадачность на основе процессов — этосредство, благодаря которому на компьютере могут параллельно выполнятьсядве программы и более.

При организации многозадачности на основе процессов программа являетсянаименьшей единицей кода, выполнение которой может координироватьпланировщик задач.

Поток представляет собой координируемую единицу исполняемого кода.

При организации многозадачности на основе потоков у каждого процесса долженбыть по крайней мере один поток, хотя их может быть и больше. Это означает,что в одной программе одновременно могут решаться две задачи и больше.

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

Page 13: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 13

Основы многопоточности

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

В среде .NET Framework определены две разновидности потоков: приоритетный ифоновый. По умолчанию создаваемый поток автоматически становитсяприоритетным, но его можно сделать фоновым. Единственное отличиеприоритетных потоков от фоновых заключается в том, что фоновый потокавтоматически завершается, если в его процессе остановлены всеприоритетные потоки.

В связи с организацией многозадачности на основе потоков возникает потребность вособого рода режиме, который называется синхронизацией и позволяеткоординировать выполнение потоков вполне определенным образом. Для такойсинхронизации в С# предусмотрена отдельная подсистема.

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

Page 14: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 14

Запуск потоков

class MutihtreadingClass

{

public static void ThreadFunction()

{

Console.WriteLine("Hello, World from Thread #{0}.",

Thread.CurrentThread.GetHashCode());

}

}

class Program

{

static void Main(string[] args)

{

Thread newThread =

new Thread(new ThreadStart(MutihtreadingClass.ThreadFunction));

newThread.Start();

newThread.Join();

MutihtreadingClass.ThreadFunction();

Console.WriteLine("All Threads Finished.");

Console.ReadKey();

}

}Hello, World from Thread #10.

Hello, World from Thread #9.

All Threads Finished.

Page 15: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 15

Передача данных потокам и получение результата

class MutihtreadingClass {

public int ThreadData { get; set; }

public int ThreadResult { get; set; }

public void ThreadFunction() {

Console.WriteLine("Hello, World from Thread #{0}.",

Thread.CurrentThread.GetHashCode());

Console.WriteLine("ThreadData= {0}.", ThreadData);

ThreadResult = 54321;

}

}

class Program {

static void Main(string[] args) {

MutihtreadingClass mtClass=new MutihtreadingClass();

mtClass.ThreadData=12345;

ThreadStart threadFunc = mtClass.ThreadFunction;

Thread newThread = new Thread(threadFunc);

newThread.Start();

newThread.Join();

Console.WriteLine("ThreadResult= {0}.", mtClass.ThreadResult);

Console.ReadKey();

}

} Hello, World from Thread #9.

ThreadData= 12345.

ThreadResult= 54321.

Page 16: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 16

Использование ParameterizedThreadStart

class MutihtreadingClass

{

public static void ThreadFunction(object Data)

{

Console.WriteLine("Hello, World from Thread #{0}.",

Thread.CurrentThread.GetHashCode());

Console.WriteLine("Data= {0}.", Data.ToString());

}

}

class Program

{

static void Main(string[] args)

{

ParameterizedThreadStart parStart =

MutihtreadingClass.ThreadFunction;

Thread newThread = new Thread(parStart);

newThread.Start((object)10);

newThread.Join();

Console.ReadKey();

}

}Hello, World from Thread #10.

Data= 10.

Page 17: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 17

Состояние потока

class Thread{

public ThreadState ThreadState { get; }

}

public enum ThreadState;

AbortedСостояние потока включает в себя значение AbortRequested, и поток теперь не выполняет работу, но его состояние еще не изменилось на Stopped.

AbortRequestedМетод Thread.Abort был вызван для потока, но поток еще не получил исключение System.Threading.ThreadAbortException, которое попытается завершить его.

BackgroundПоток выполняется как фоновый поток, в противоположность потокам переднего плана. Это состояние управляется заданием свойства Thread.IsBackground.

RunningПоток был запущен, он не заблокирован, и нет ожидающего исключения ThreadAbortException.

Stopped Поток был остановлен.

StopRequestedПоток получает запрос на остановку. Предназначено только для внутреннего использования.

Suspended Поток был приостановлен.

SuspendRequested Запрашивается приостановка работы потока.

Unstarted Метод Thread.Start не был вызван для потока.

WaitSleepJoin

Поток заблокирован. Это может произойти в результате вызова метода Thread.Sleep или метода Thread.Join, в результате запроса блокировки, например при вызове метода Monitor.Enter или Monitor.Wait или в результате ожидания объекта синхронизации потока, такого как ManualResetEvent.

Page 18: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 18

Завершение потоков

class MutihtreadingClass

{

public static void ThreadFunction(object Data)

{

Console.WriteLine("Hello, World from Thread #{0}.",

Thread.CurrentThread.GetHashCode());

Console.WriteLine("Data= {0}.", Data.ToString());

}

}

class Program

{

static void Main(string[] args)

{

ParameterizedThreadStart parStart =

MutihtreadingClass.ThreadFunction;

Thread newThread = new Thread(parStart);

newThread.Start((object)10);

newThread.Abort();

newThread.Join();

Console.ReadKey();

}

}

Page 19: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 19

Использование ThreadPool

class MutihtreadingClass

{

public static void ThreadFunction(object Data)

{

Console.WriteLine("Hello, World from Thread #{0}.",

Thread.CurrentThread.GetHashCode());

Console.WriteLine("Data= {0}.", Data.ToString());

}

}

class Program

{

static void Main(string[] args)

{

WaitCallback wc = MutihtreadingClass.ThreadFunction;

ThreadPool.QueueUserWorkItem(wc, 10);

Console.ReadKey();

}

}

Hello, World from Thread #9.

Data= 10.

Page 20: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 20

Синхронизация работы потоков

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

Класс, элементы которого защищены от подобных прерываний, называетсяпотокобезопасным.

Общеязыковая инфраструктура содержит несколько способов синхронизациидоступа к статическим элементам и элементам экземпляров.

• Синхронизованные области кода. Для синхронизации только требуемыхблоков кода в целях улучшения производительности можновоспользоваться классом Monitor или поддержкой компилятора для этогокласса.

• Синхронизация вручную. Можно использовать объекты синхронизации,предоставленные в библиотеке классов .NET Framework.

• Коллекция классов в пространстве имен System.Collections.Concurrent. Этиклассы предоставляют синхронизированные операции добавления иудаления.

Page 21: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 21

Оператор lock

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

Для блокировки рекомендуется определять объект private илипеременную объекта private static, чтобы защитить данные,являющиеся общими для всех экземпляров.

Page 22: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 22

Оператор lock

class MutihtreadingClass {

public object ThreadData { get; set; }

public void ThreadFunction(object Data) {

lock (ThreadData)

{

Console.WriteLine("Hello, World from Thread #{0}.",

Thread.CurrentThread.GetHashCode());

ThreadData = ((int)ThreadData + (int)Data);

Console.WriteLine("ThreadData= {0}.", ThreadData);

}

}

}

class Program

{

static void Main(string[] args) {

MutihtreadingClass mtc = new MutihtreadingClass();

mtc.ThreadData = 10;

WaitCallback wc = mtc.ThreadFunction;

ThreadPool.QueueUserWorkItem(wc, 15);

ThreadPool.QueueUserWorkItem(wc, 25);

Console.ReadKey();

}

}

Hello, World from Thread #12.

ThreadData= 35. // или 25

Hello, World from Thread #11.

ThreadData= 50.

Page 23: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 23

Асинхронный вызов методов

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

При объявлении делегата CLR создает класс-наследникSystem.MulticastDelegate. Один из определенных в нем методов,Invoke, принимает точно ту же сигнатуру функции, что и определениеделегата. Разумеется в С# предусмотрено синтаксическое сокращениедля вызова метода Invoke. Но наряду с Invoke среда CLR такжеопределяет два метода Beginlnvoke и Endlnvoke, являющиесясердцем шаблона асинхронной обработки, которая используется CLR.

Базовая идея очевидным образом исходит из имен методов. При вызовеBeginlnvoke на делегате операция откладывается для выполнения вдругом потоке. Вызове метода Endlnvoke приводит к возвратурезультатов операции. Если операция не завершена на момент вызоваEndlnvoke, вызывающий поток блокируется до тех пор, пока она небудет завершена.

Page 24: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 24

Пример асинхронного вызова

class MutihtreadingClass {

public static void ThreadFunction(object Data) {

Console.WriteLine("Hello, World from Thread #{0}.",

Thread.CurrentThread.GetHashCode());

Console.WriteLine("Data= {0}.", Data.ToString());

Thread.Sleep(5000);

Console.WriteLine("Thread #{0} Finished.",

Thread.CurrentThread.GetHashCode());

}

}

class Program {

private delegate void AsyncMethodCall(object obj);

static void Main(string[] args) {

AsyncMethodCall amc =

new AsyncMethodCall(MutihtreadingClass.ThreadFunction);

IAsyncResult asyncResult = amc.BeginInvoke(10, null, null);

Console.WriteLine("Doing Something Else...");

Thread.Sleep(3000);

Console.WriteLine("Waiting for Thread...");

amc.EndInvoke(asyncResult);

Console.ReadKey();

}

}

Doing Something Else...

Hello, World from Thread #10.

Data= 10.

Waiting for Thread...

Thread #10 Finished.

Page 25: C# Desktop. Занятие 15

http://www.slideshare.net/IgorShkulipa 25

Лабораторная работа №15. Многопоточность в C#

В индивидуальных курсовых проектах при необходимости использоватьсоздание потоков с помощью классов Thread или ThreadPool, а так жеасинхронный вызов методов.