Делегаты каркаса .NET: Основные сведения об асинхронных делегатах

ОГЛАВЛЕНИЕ

Статья объясняет делегаты и способы их асинхронного использования.

Введение

Делегаты – специальные типы, поддерживаемые каркасом .NET, представляющие собой сильно типизированную сигнатуру метода. Делегаты могут инстанцироваться и формироваться поверх комбинации целевого метода и экземпляра, где метод соответствует сигнатуре метода. C# позволяет создавать специальные классы с помощью ключевого слова delegate. Такой класс называется классом делегата. Экземпляры классов делегатов называются объектами делегатов. Объект делегата является ссылкой на один или несколько методов (статических или экземплярных). Синтаксис вызова объекта делегата идентичен синтаксису вызова метода. Это инициирует вызов метода. Вызов этих методов производится тем же потоком, который вызвал объект делегата. Это называется синхронным вызовом. Но при осуществлении синхронного вызова метода вызывающий поток блокируется, пока вызов активен. Пока поток заблокирован, он может создавать другие потоки, несмотря на то, что процессор бездействует. Хотя эти потоки не используют процессор, они расходуют ресурсы, что не экономично. Когда поток делает асинхронный вызов метода, вызов сразу возвращается.

Вызывающий поток не блокируется; он может выполнять какую-то иную задачу. Инфраструктура .NET получает поток для вызова метода и доставляет входной параметр, переданный вызывающим кодом. Затем асинхронный поток может выполнять метод параллельно вызывающему потоку. Если метод генерирует некие данные и возвращает это значение, вызывающий поток должен иметь доступ к этим данным. Асинхронное свойство .NET поддерживает два механизма: вызывающий поток может запросить результаты, или инфраструктура может доставить результаты вызывающему потоку, когда результаты готовы. Цель данной статьи – объяснить делегаты и способы их асинхронного использования.

Делегаты

В C# новый тип делегата создается с помощью ключевого слова delegate:

public delegate void MyDelegate(int x, int y);

Был создан новый тип делегата по имени MyDelegate, созданный поверх методов с типами возвращаемой переменной void(пустой) и принимающий два аргумента типа int. Затем делегат может быть сформирован поверх цели, передан и вызван когда-нибудь позже. Вызов в C# выглядит как обычный вызов функции:

class Foo
{
   void PrintPair(int a, int b)
   {
     Console.WriteLine("a = {0}", a);
     Console.WriteLine("b = {0}", b);
   }

   void CreateAndInvoke()
   {
     // подразумевается 'new MyDelegate(this.PrintPair)':
     MyDelegate del = PrintPair;
     del(10, 20);
  }
}

Еще не понимаете? CreateAndInvoke создает новый MyDelegate, сформированный поверх метода PrintPair с текущим указателем this в качестве цели. Реальный промежуточный язык, порожденный компилятором C#, показывает ряд сложностей делегатов в нижележащей системе типов:

struct MyDelegate :  System.MulticastDelegate
{
  public MyDelegate(object target, IntPtr, methodPtr);

  private object target;
  private IntPtr methodPtr;

  public internal void Invoke(int x, int y)
  public internal System.IAsyncResult BeginInvoke(int x, int y,
                  System.IAsyncCallback callback, object state);
  public internal void EndInvoke(System.IAsyncResult  result);

}

Конструктор используется для формирования делегата поверх целевого объекта и указателя функции. Методы Invoke, BeginInvoke и EndInvoke реализуют операцию вызова делегата и помечены как внутренние (т.е. времени выполнения на промежуточном языке), чтобы указать CLR(общеязыковая среда исполнения), что она предоставляет реализацию; их тела IL(промежуточный язык) оставляются пустыми. Invoke выполняет синхронный вызов, тогда как функции BeginInvoke и EndInvoke следуют схеме модели асинхронного программирования. Тип MyDelegate нарушает правило в том, что структуры не могут наследоваться от других типов, отличных от System.ValueType. Делегаты имеют специальную поддержку в обобщённой системе типизации (CTS), поэтому это допустимо. MyDelegate унаследован от MulticastDelegate; этот тип является общей базой для всех делегатов, созданных в C#, и поддерживает делегаты, имеющие несколько целей.

Рассмотрим создание еще одного делегата:

public delegate int StringDelegate(string str);

Его можно объявить в классе или в глобальной области видимости. Компилятор C# сгенерирует новый класс на основе этого объявления и унаследует его от System.MulticastDelegate. Рассмотрим методы данного класса и его базового класса System.Delegate (снова):

public sealed class StringDelegate : System.MulticastDelegate
{
   public StringDelegate (object obj, int method);
   public virtual int Invoke(string str);
   public virtual IAsyncResult BeginInvoke(string str,
      AsyncCallback asc, object stateObject);
   public virtual int EndInvoke(IAsyncResult result);
}
Теперь рассмотрим код, относящийся к созданию первого делегата MyDelegate:
using System;
public static class App {
   public delegate void MyDelegate(int x, int y);
   public static void PrintPair(int a, int b)
   {
     Console.WriteLine("a = {0}", a);
     Console.WriteLine("b = {0}", b);
   }
   public static void Main()
   {
     // предполагается 'new MyDelegate(this.PrintPair)':
     MyDelegate d = PrintPair;
     // предполагается 'Invoke':
     d(10, 20);
   }
}

При компиляции он порождает следующий вывод:

a = 10
b = 20

Внутренности делегатов

Был объявлен тип MyDelegate на C# так:

delegate string MyDelegate(int x);

Он представляет собой тип указателя функции, который может ссылаться на любой метод, принимающий единственный целый аргумент и возвращающий строку. При работе с экземпляром данного типа делегата объявляются переменные типа MyDelegate. Тайно компилятор генерирует новый класс (тип):

private sealed class MyDelegate : MulticastDelegate
{
    public extern MyDelegate(object object, IntPtr method);
    public extern virtual string Invoke(int x);
    public extern virtual IAsyncResult BeginInvoke(int x,
                  AsyncCallback  callback, object   object);
    public extern virtual string Endinvoke((IAsyncResult result);
}

Допустим, что есть пользовательский тип MyType с методом MyFunc, сигнатура которого точно совпадает с MyDelegate. Заметьте, что параметры не названы идентично. Это нормально, так как делегаты лишь требуют, чтобы ожидаемые типы находились в нужных позициях сигнатуры:

class MyType
{
  public string MyFunc(int foo)
  {
     return "MyFunc called with the value  '" + foo + "' foo foo;
  }
}

Имея тип делегата в метаданных и вызываемую целевую функцию, формируем экземпляр делегата поверх цели. Это создает новый экземпляр типа делегата с помощью конструктора MyDelegate(object, IntPtr). Код передает цель в качестве первого аргумента и указатель на функцию кода в виде второго аргумента. Синтаксис для этого таков:

MyType mt = new MyType();
MyDelegate md = mt.MyFunc;
Объединим все части вместе:
using System;
delegate string MyDelegate(int x);
class MyType
{
  public string MyFunc(int foo)
  {
     return "MyFunc called with the value '" + foo + "' for foo";
  }
}

public class Program {
  public static void Main()
  {
    MyType mt = new MyType();
    MyDelegate md = mt.MyFunc;
    Console.WriteLine(md.Invoke(5));
    Console.WriteLine(md(5));
  }
}

Код компилируется и выдает такой результат:

MyFunc вызвана со значением '5' для foo
MyFunc вызвана со значением '5' для foo