Рабочим названием платформы .NET было |
Параллельные операции в .NET
Асинхронные процедуры
Для реализации вызова асинхронных процедур в .NET используются фоновые потоки пула, так же как для обработки асинхронных операций ввода-вывода. Класс ThreadPool предлагает два способа для вызова асинхронных процедур: явное размещение вызовов в очереди ( QueueUserWorkItem ) и связывание вызовов с переводом некоторых объектов в свободное состояние ( RegisterWaitForSingleObject ). Кроме того, .NET позволяет осуществлять асинхронные вызовы любых процедур с помощью метода BeginInvoke делегатов.
Статический метод ThreadPool.QueueUserWorkItem ставит вызов указанной процедуры в очередь для обработки. Если пул содержит простаивающие потоки, то обработка этой функции начнется немедленно:
using System; using System.Threading; namespace TestNamespace { class GreetingData { private string m_greeting; public GreetingData( string text ) { m_greeting = text; } public void Invoke() { Console.WriteLine( m_greeting ); } } class TestApp { static void AsyncProc( Object arg ) { GreetingData gd = (GreetingData)arg; gd.Invoke(); } public static void Main() { GreetingData gd = new GreetingData("Hello, world!"); ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncProc), gd); Thread.Sleep( 1000 ); } } }
При постановке в очередь асинхронного вызова можно указать объект, который является аргументом асинхронной процедуры (при создании собственных потоков передача аргументов процедуре потока затруднительна).
Второй способ вызова асинхронных процедур связан с использованием объектов, производных от класса System.Threading.WaitHandle (это события и мьютексы). При этом вызов асинхронной процедуры связывается с переводом объекта в свободное состояние. Данный метод может быть использован также для организации повторяющегося через определенные интервалы вызова асинхронных процедур - при регистрации делегата указывается максимальный интервал ожидания, и если он исчерпывается, то вызов размещается в очереди пула, даже если объект остался занятым. Если объект по-прежнему остается занятым, то вызов процедуры будет периодически размещаться в очереди после исчерпания каждого интервала ожидания.
using System; using System.Threading; namespace TestNamespace { class GreetingData { private string m_greeting; private RegisteredWaitHandle m_waithandle; public GreetingData( string text ) { m_greeting = text; } public void Invoke() { Console.WriteLine( m_greeting ); } public RegisteredWaitHandle WaitHandle { set { if (value==null) m_waithandle.Unregister( null ); m_waithandle = value; } } } class TestApp { static void AsyncProc( Object arg, bool isTimeout ) { GreetingData gd = (GreetingData)arg; if ( !isTimeout ) gd.WaitHandle = null; gd.Invoke(); } public static void Main() { GreetingData gd = new GreetingData("Hello"); AutoResetEvent ev = new AutoResetEvent(false); gd.WaitHandle=ThreadPool.RegisterWaitForSingleObject( ev, new WaitOrTimerCallback(AsyncProc), gd, 1000, false ); Thread.Sleep( 2500 ); ev.Set(); Console.ReadLine(); } } }
Приведенный пример демонстрирует использование периодического вызова асинхронной процедуры - при регистрации делегата ( RegisterWaitForSingleObject ) указывается максимальное время ожидания 1 секунда (1000 миллисекунд), после чего основной поток переводится в состояние "спячки" на 2.5 секунды. За это время в очередь пула поступает два вызова асинхронных процедур (с признаком вызова по тайм-ауту). Через 2.5 секунды основной поток пробуждается, переводит событие в свободное состояние, и в очередь пула поступает третий вызов. При обработке этого вызова регистрация делегата отменяется.
Последний способ связан с использованием методов BeginInvoke и EndInvoke делегатов. Когда определяется какой-либо делегат функции, для него будут определены методы: BeginInvoke (содержащий все аргументы делегата плюс два дополнительных - AsyncCallback, который может быть вызван по завершении обработки асинхронного вызова, и AsyncState, с помощью которого можно определить состояние асинхронной процедуры) и EndInvoke, содержащий все выходные параметры (т.е. описанные как inout или out ), плюс IAsyncResult, позволяющий узнать результат выполнения процедуры.
Таким образом, использование BeginInvoke позволяет не только поставить в очередь вызов асинхронной процедуры, но также связать с завершением ее обработки еще один асинхронный вызов. Метод EndInvoke служит для ожидания завершения обработки асинхронной процедуры:
using System; using System.Threading; namespace TestNamespace { public class GreetingData { private string m_greeting; public GreetingData( string text ) { m_greeting = text; } public static void Invoke( GreetingData arg ) { Console.WriteLine( arg.m_greeting ); } } public delegate void AsyncProcCallback ( GreetingData gd ); class TestApp { public static void Main() { GreetingData gd = new GreetingData( "Hello!!!" ); AsyncProcCallback apd = new AsyncProcCallback( GreetingData.Invoke ); IAsyncResult ar = apd.BeginInvoke( gd, null, null ); ar.AsyncWaitHandle.WaitOne(); } } }
Данный пример иллюстрирует вызов асинхронной процедуры с использованием метода BeginInvoke и альтернативный механизм ожидания завершения - с использованием внутреннего объекта AsyncWaitHandle (класса WaitHandle ), благодаря которому, собственно говоря, становится возможен вызов асинхронной процедуры, обслуживающей завершение обработки данной процедуры.
В этом смысле асинхронный вызов процедур с помощью BeginInvoke очень близок к обработке асинхронных операций ввода-вывода.