| Рабочим названием платформы .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 очень близок к обработке асинхронных операций ввода-вывода.