Приветствую всех, сегодня поговорим о продолжении темы связанной с потоками, это часть 4. Читать Потоки ч.1 Читать Потоки ч.2 Читать Потоки ч.3
Эта часть будет посвящена асинхронному программированию. Главная идея асинхронного программирования заключается в том, чтобы запускать отдельные
вызовы методов и параллельно продолжать выполнять другую работу без ожидания окончания вызовов. Локальные методы, вероятность исключений которых сведена к минимуму, не нуждаются в асинхронном подходе (например, изменение цвета шрифта текста или его размера), но другие методы (ожидание чтения файла или запуск web-службы) требуют его в самом начале разработки. Среда Common Language Runtime поддерживает достаточно широкое количество методов и классов асинхронного программирования. Классы, в которых есть встроенная поддержка асинхронной модели, имеют пару асинхронных методов для каждого из синхронных методов. Эти методы начинаются со слов Begin и End. Например, если мы хотим воспользоваться асинхронным вариантом метода Read класса System.IO.Stream, нам нужно использовать методы BeginRead и EndRead этого же класса.
Асинхронное программирование — это возможность, поддерживаемая многими областями платформы .NET Framework, в том числе:
- ввод-вывод на основе файлов, потоков и сокетов;
- работа в сети;
- удаленные каналы (HTTP, TCP) и прокси-сервера;
- веб-службы с поддержкой XML, создаваемые с помощью ASP.NET;
- веб-формы ASP.NET;
- организация очередей сообщений с помощью класса MessageQueue.
Делегаты позволяют вызывать синхронные методы асинхронно. При вызове метода Invoke(), вызывается метод сообщенный с делегатом в контексте текущего потока (синхронно). При вызове метода BeginInvoke(), метод сообщенный с делегатом помещается в пул потоков и вызывается асинхронно. Если при вызове метода BeginInvoke() был задан метод обратного вызова (callback), он будет вызван при завершении выполнения метода вызванного асинхронно. В методе обратного вызова метод EndInvoke() получает возвращаемое значение и любые входные и выходные параметры или только выходные параметры. Если метод обратного вызова не указан при вызове BeginInvoke(), EndInvoke() может быть вызван из первичного потока, который вызвал BeginInvoke().
Для использования встроенной поддержки асинхронной модели программирования нужно вызвать соответствующий метод BeginOperation и выбрать модель завершения вызова. Вызов метода BeginOperation возвращает объект интерфейса IAsyncResult, с помощью которого определяется состояние выполнения асинхронной операции.
Метод EndOperation применяется для завершения асинхронного вызова в тех случаях, когда основному потоку необходимо проделать большой объем вычислений, не зависящих от результатов вызова асинхронного метода. После того как основная работа сделана и приложение нуждается в результатах выполнения асинхронного метода для дальнейших действий, вызывается метод EndOperation. При этом основной поток будет приостановлен до завершения работы асинхронного метода.
Способ завершения асинхронного вызова Callback используется в тех случаях, когда нужно предотвратить блокирование основного потока. При использовании Callback мы запускаем метод EndOperation в теле метода, который вызывается при завершении метода, работающего в параллельном потоке. Сигнатура вызываемого метода должна совпадать с сигнатурой делегата AsyncCallback.
Вызов асинхронных делегатов позволяет неявно помещать потоки в ThreadPool, тем самым избавляя программиста от необходимости работать с ним напрямую. Сигнатура метода BeginInvoke не соответствует методу Invoke. Это объясняется тем, что нужен некоторый способ идентификации определенного элемента работы, который только что был отложен вызовом Beginlnvoke. Таким образом, Beginlnvoke возвращает ссылку на объект, реализующий интерфейс IAsyncResult. Этот объект подобен cookie-набору, который сохраняется для идентификации выполняющегося элемента работы. Через методы интерфейса IAsyncResult можно проверять состояние операции, например, ее готовность.
Когда поток, запрошенный для выполнения операции, завершит свою работу, он вызывает Endlnvoke на делегате. Однако, поскольку метод должен иметь способ идентификации асинхронной операции, результат которой нужно получить, ему должен быть передан объект, полученный из метода Beginlnvoke.
Если в процессе асинхронного выполнения в пуле потоков целевого кода делегата будет сгенерировано исключение, оно сгенерируется повторно, когда инициирующий поток вызовет EndInvoke.
Пул потоков обладает следующими преимуществами:
- Пул потоков управляет потоками эффективно, уменьшая количество создаваемых, запускаемых и останавливаемых потоков.
- Используя пул потоков, можно сосредоточиться на решении задачи, а не на инфраструктуре потоков приложения.
Ситуации, в которых предпочтительно ручное управление потоками:
- Если нужны потоки переднего плана, или должен быть установлен приоритет потока. Внимание: Потоки из пула всегда являются фоновыми с приоритетом по
умолчанию (ThreadPriority.Normal). - Если требуется поток с фиксированной идентичностью, чтобы можно было прерывать его или находить по имени.
Интерфейс IAsyncResult реализован с помощью классов, содержащих методы, которые могут работать асинхронно. Объект, который обеспечивает работу интерфейса IAsyncResult, хранит в себе сведения о состоянии асинхронной операции и предоставляет объект синхронизации, сигнализирующий потоку о завершении операции.
Для обработки результатов асинхронной операции в отдельном потоке используется делегат AsyncCallback. Делегат AsyncCallback представляет метод обратного вызова, который вызывается при завершении асинхронной операции. Метод обратного вызова принимает параметр IAsyncResult, который впоследствии используется для получения результатов асинхронной операции.
Класс AsyncResult используется в сочетании с асинхронными вызовами методов с помощью делегатов. IAsyncResult, возвращенный из делегатского метода BeginInvoke, можно привести к AsyncResult. AsyncResult имеет свойство AsyncDelegate, содержащее объект делегата, к которому был направлен асинхронный вызов.
Простой пример использования делегата Action для асинхронного выполнения метода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Prog { public static void Task1() { for (int i = 0; i < 80; i++) { Thread.Sleep(100); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write("*"); } } static void Main(string[] args) { Action dl = new Action(Task1); dl.BeginInvoke(null,null); for (int i = 0; i < 80; i++) { Thread.Sleep(100); Console.ForegroundColor = ConsoleColor.Blue; Console.Write("-"); } Console.ReadKey(); } } |
Пример использования делегата Func в асинхронном программировании с возможностью передачи параметров в метод и получения результата его работы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Prog { public static int Task1(int a, int b) { return a * b; } static void Main(string[] args) { Func<int, int, int> dl = new Func<int,int,int>(Task1); IAsyncResult asyncResult = dl.BeginInvoke(2,3,null,null); int result = dl.EndInvoke(asyncResult); Console.WriteLine("Ресультат= {0}",result); Console.ReadKey(); } } |
аналогичный пример, только с возможностью использования сигнального состояния, приостановкой работы главного потока, до завершения асинхронного потока.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Prog { public static int Task1(int a, int b) { Thread.Sleep(10000); return a + b; } static void Main(string[] args) { Func<int, int, int> dl = new Func<int,int,int>(Task1); IAsyncResult asyncResult = dl.BeginInvoke(2,3,null,null); Console.WriteLine("Запустили поток и ожидаем его выполнения"); asyncResult.AsyncWaitHandle.WaitOne(); int result = dl.EndInvoke(asyncResult); asyncResult.AsyncWaitHandle.Close(); Console.WriteLine("Ресультат= {0}",result); Console.ReadKey(); } } |
Пример использования метода обработки завершения асинхронной операции CallBack, он в свою очередь будет вызван после завершения метода Task1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using System; using System.Threading; using System.Runtime.Remoting.Messaging; namespace ConsoleApp50 { class Prog { public static int Task1(int a, int b) { Thread.Sleep(2000); return a * b; } static void CallBack(IAsyncResult iAsyncResult) { var asyncResult = iAsyncResult as AsyncResult; var caller = (Func<int, int, int>)asyncResult.AsyncDelegate; int sum = caller.EndInvoke(iAsyncResult); string result = string.Format(iAsyncResult.AsyncState.ToString(), sum); Console.WriteLine("Результат асинхронной операции: " + result); } static void Main(string[] args) { Func<int, int, int> dl = new Func<int,int,int>(Task1); IAsyncResult asyncResult = dl.BeginInvoke(2,3,CallBack,"a + b = {0}"); Console.WriteLine("Запустили поток и ожидаем его выполнения"); Console.ReadKey(); } } } |
Метод позволяющий асинхронное считывания файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static void Main() { var stream = new FileStream("test.txt", FileMode.Open, FileAccess.Read); var array = new byte[stream.Length]; IAsyncResult asyncResult = stream.BeginRead(array, 0, array.Length, null, null); Console.WriteLine("Чтение файла ..."); stream.EndRead(asyncResult); foreach (byte item in array) Console.Write(item+" "); stream.Close(); Console.ReadKey(); } |
Метод позволяющий асинхронное записывать в файл:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static void Main() { var stream = new FileStream("test.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite); byte[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; stream.BeginWrite(array, 0, array.Length, new AsyncCallback(CleanUp), stream); Console.ReadKey(); } static void CleanUp(IAsyncResult asyncResult) { Console.WriteLine("Файл записан."); var stream = asyncResult.AsyncState as FileStream; if (stream != null) stream.Close(); } |