Где задокументированы определенные в среде CLR методы, такие как [delegate].BeginInvoke?

[EDIT, полностью перефразировано:] Похоже, мой вопрос был действительно плохо сформулирован и плохо принят. Поэтому я надеюсь, что эта полная перефразировка поможет...

В сообщении MSDN четко указано: Control.BeginInvoke() делегировать в потоке, в котором был создан дескриптор элемента управления, обычно это будет поток GUI. И Dispatcher.BeginInvoke() будет выполняться в потоке, где создан объект Диспетчер. Это будет любой поток, созданный мной.

Но для делегатов "CLR автоматически определяет BeginInvoke и EndInvoke", и вместо этого эти вызовы выполняются в потоке ThreadPool. Помимо этого несколько удивительного поведения, мне интересно, как я могу найти спецификации всех функций, которые автоматически реализуются.

Например: Intelli-sense показывает, что у моего делегата есть DynamicInvoke(). Класс System.Delegate{} имеет DynamicInvoke(), что может означать, что мой делегат наследует его . Но у Delegate{} нет BeginInvoke(). А у Delegate{} есть несколько функций, которых нет у моего делегата. Также мой делегат получает метод GetObjectData(). И это, кажется, исходит от ISerializable.

Таким образом, делегат получает свои методы из (1) среды CLR «автоматически», (2) некоторого подмножества Delegate{}, возможно, MulticastDelegate{} и, возможно, (3) ISerializble. Где я могу найти исчерпывающую спецификацию всех методов, которые получает делегат? Особенно интересен метод BeginInvoke() и его точная сигнатура, поскольку два вышеупомянутых метода с таким именем имеют разные наборы сигнатур.

[Кто-то предложил в редактировании, что "делегат" - это "Делегат". Осмелюсь сказать, что это не так.]

Спасибо


person Adam    schedule 19.02.2013    source источник
comment
Звучит как вопрос, который больше подходит для форумов MSDN   -  person chris    schedule 19.02.2013
comment
Ваш вопрос Почему документация Microsoft отстой? Если так, то это ругательство. Есть актуальный вопрос?   -  person David Hope    schedule 19.02.2013
comment
Найдите ThreadPool в упомянутой вами статье MSDN. Это совершенно ясно.   -  person Simon Mourier    schedule 19.02.2013
comment
Может быть, я упускаю суть, но вся цель BeginInvoke состоит в том, чтобы поддерживать инфраструктуру запуска потоков, и эти потоки берутся из ThreadPool, и хотя большинство документов MS оставляют желать много лучшего , я думаю, они довольно ясно упоминают об этом...? Не получая разглагольствования.   -  person David W    schedule 19.02.2013
comment
«Метод BeginInvoke инициирует асинхронный вызов». Как вы думаете, что именно означает асинхронность? Это просто начало неблокирующего вызова, это означает, что нужен новый поток (угадайте, почему там упоминается пул потоков...) Это должно быть ясно из первого предложения в статье... Это даже объясняется, если вы просто сделаете шаг в одной статье. до msdn.microsoft.com/en-us/library/22t547yb.aspx -> информация о главе, на которую вы ссылаетесь. Хорошо, когда в статьях не так много лишней информации.   -  person Offler    schedule 19.02.2013
comment
Дэвид, это не разглагольствования. Вопрос: где спецификация?   -  person Adam    schedule 20.02.2013
comment
Offler, некоторые вызовы выполняются в определенном потоке, некоторые вызовы выполняются в [потоке из] пула потоков. Для меня не так очевидно.   -  person Adam    schedule 20.02.2013
comment
Крис, да, но у меня там нет учетной записи, и этот форум кажется более ярким.   -  person Adam    schedule 20.02.2013


Ответы (2)


Методы Control.Begin/End/Invoke() и Dispatcher.Begin/End/Invoke() имеют идентичные имена и в чем-то похожи на методы делегата Begin/End/Invoke(), но определенно лучше отказаться от идеи, что они подобные. Наиболее важным отличием является то, что методы делегата являются типобезопасными, что полностью отсутствует в версиях Control и Dispatcher. Поведение во время выполнения также сильно отличается.

Правила, управляющие делегатом, подробно описаны в спецификации CLI, ECMA 335, глава II.14.6. Лучше всего прочитать главу, я приведу лишь синопсис.

Объявление делегата преобразуется в класс, наследуемый от MulticastDelegate (а не от Delegate, как указано в спецификации CLI). Этот класс всегда имеет ровно 4 члена, их реализация во время выполнения обеспечивается CLR:

  • конструктор, который принимает объект и IntPtr. Объект — Delegate.Target, IntPtr — адрес целевого метода Delegate.Method. Эти члены используются позже при вызове делегата. Свойство Target предоставляет ссылку this, если метод, к которому привязан делегат, является методом экземпляра, null для статического метода. Свойство Method определяет, какой метод вызывается. Вы не указываете эти аргументы напрямую, их предоставляет компилятор, когда вы используете оператор new или подписываетесь на обработчик событий с помощью оператора +=. С большим количеством синтаксического сахара в случае событий вам не нужно явно использовать оператор new.

  • метод Вызвать(). Аргументы метода генерируются динамически и соответствуют объявлению делегата. Вызов метода Invoke() запускает целевой метод делегата в том же потоке, что является синхронным вызовом. Вы редко используете его в C#, вы просто используете синтаксический сахар, который позволяет вызывать объект делегата, просто используя имя объекта, за которым следуют круглые скобки.

  • Метод BeginInvoke() предоставляет способ выполнения асинхронного вызова. Метод быстро завершается, пока целевой метод занят выполнением, аналогично ThreadPool.QueueUserWorkItem, но с аргументами, безопасными для типов. Тип возвращаемого значения всегда System.IAsyncResult, который используется для определения момента завершения асинхронного вызова и передается методу EndInvoke(). Первый аргумент — это необязательный объект делегата System.AsyncCallback, его цель будет автоматически вызвана после завершения асинхронного вызова. Второй аргумент является необязательным объектом, он будет передан обратному вызову как есть, что полезно для отслеживания состояния. Дополнительные аргументы генерируются динамически и соответствуют объявлению делегата.

  • метод EndInvoke(). Он принимает один аргумент типа IAsyncResult, вы должны передать тот, который вы получили от BeginInvoke(). Он завершает асинхронный вызов и освобождает ресурсы.

Любые дополнительные методы, которые вы видите в объекте делегата, унаследованы от базовых классов MulticastDelegate и Delegate. Как DynamicInvoke() и GetObjectData().

Асинхронные вызовы сложны, и вам редко нужно их использовать. На самом деле они недоступны в целях .NETCore, таких как Silverlight. Целевой метод делегата выполняется в произвольном потоке пула потоков, как это делает Threadpool.QueueUserWorkItem(). Любое необработанное исключение, которое он может вызвать, захватывается и завершает поток, но не вашу программу. Вы должны вызвать EndInvoke(), иначе это приведет к утечке ресурсов на 10 минут. Если целевой метод выдал исключение, оно будет повторно вызвано при вызове EndInvoke(). У вас нет контроля над потоком пула потоков, его невозможно отменить или прервать. Классы Task или Thread являются лучшими альтернативами.

MSDN актуален, методы типа делегата не документированы. Предполагается, что вы знаете, что они делают и как они выглядят, из спецификации и объявления делегата.

person Hans Passant    schedule 21.02.2013
comment
Спасибо за отличный ответ. Этот CLI-материал — золотая жила. - person Adam; 25.02.2013
comment
FWIW, я бы не согласился с утверждением, что методы типа делегата не документированы. То есть, хотя у каждого конкретного типа делегата нет документации, MSDN включает общую документацию, которая точно описывает, что делают эти методы. См., например. msdn.microsoft.com/en-us/library /22t547yb(v=vs.110).aspx и msdn.microsoft.com/en-us/library/2e08f6yc(v=vs.110).aspx - person Peter Duniho; 07.09.2016
comment
Погуглите, скажем, methodinvoker.begininvoke. Первое обращение к MSDN относится к Control.BeginInvoke. КЭД. - person Hans Passant; 07.09.2016

В соответствии с предметом вашего вопроса ответом будут эти жирные линии. MSDN может быть не лучше, но это хорошо :)

Джеффри Рихтер написал о том, что вы задали в своем вопросе выше. У него есть эта статья в журнале MSDN. http://msdn.microsoft.com/en-us/magazine/cc164139.aspx В этой статье будет показана реализация того, как на самом деле (может быть, не на самом деле, но очень близко) эти BeginInvoke и EndInvoke реализованы в .NET CLR. Потратьте некоторое время на эту статью, и после этого я не думаю, что вам нужно читать дальше. Джеффри Рихтер также очень хорошо объяснил все это в своей книге CLR Via C#.

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

Для этого Control.Invoke существует в Winforms. Он автоматически вызовет ваш код в потоке пользовательского интерфейса. В мире WPF у нас нет Control.Invoke. В WPF у нас есть Dispatcher вместо Control.

Теперь делегат против делегата. Ханс Пассант дал очень хороший ответ.

Поэтому, чтобы немного остановиться на этом, я пишу этот ответ.

Делегат, как упоминалось в MSDN, — это класс. Давайте возьмем этот код (взято из msdn http://msdn.microsoft.com/en-us/library/ms173171(v=vs.80).aspx )

public delegate int PerformCalculation(int x, int y);

Как видите, у нас есть делегат (обратите внимание на маленькую букву «d»). Это ключевое слово для определения делегата или, выражаясь простыми словами, это ключевое слово для определения переменной PerformCalculation, которая фактически содержит ссылку на метод.

Я думаю, вы уже знаете об этом, но только для полноты картины.

Теперь, чтобы использовать эту переменную и вызвать метод, используя такой код:

using System;
// Declare delegate -- defines required signature:
delegate void SampleDelegate(string message);

class TestDelegate
{
    private void CallMeUsingDelegate(string m_param)
    {
        Console.WriteLine("Called me using parameter - " + m_param);
    }

    public static void Main(string[] args)
    {
        // Here is the Code that uses the delegate defined above.
        SampleDelegate sd = new SampleDelegate(CallMeUsingDelegate);
        sd.Invoke("FromMain");
    }
}

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

Таким образом, приведенный выше код также можно записать как

с помощью системы; // Объявить делегата -- определяет требуемую подпись: delegate void SampleDelegate(string message);

class TestDelegate
{
    public static void Main(string[] args)
    {
        // Here is the Code that uses the delegate defined above.
        SampleDelegate sd = delegate(param) {
                        Console.WriteLine("Called me using parameter - " + param);
                    };

        sd.Invoke("FromMain");
    }
}

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

Когда дело доходит до BeginInvoke и EndInvoke, они используются для асинхронного вызова методов. Это делается с помощью пула потоков, доступного в среде CLR.

В основном, что происходит, вы вызываете метод, используя

IAsyncResult ar = sd.BeginInvoke(CallMeUsingDelegate, callMeOnCompletion, sd);

Здесь Delegate — это метод, который вы вызываете. Что произойдет, так это то, что Thread вашей программы вызовет метод BeginInvoke, который будет внутренне вызывать метод, указанный в параметре Delegate, в потоке CLR ThreadPool. Затем ваша программа продолжает работу и возвращает объект, реализующий интерфейс IAsyncResult. Вы можете использовать этот объект для запроса о ходе выполнения задачи, вызванной с помощью вашего делегата (обратите внимание, что Delegate sd передается как параметр 3).

Метод CallMeUsingDelegate вызывается в отдельном потоке (из ThreadPool). Когда задача завершится, ThreadPool вызовет метод обратного вызова, указанный в качестве параметра 2.

Глядя на все это, вы можете подумать, а зачем нам тогда EndInvoke???

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

Я надеюсь, что теперь это проясняет (не все), но некоторые мысли.

person Dinesh    schedule 24.02.2013
comment
Спасибо за эту дополнительную информацию. Рихтер, как всегда, очень хороший писатель. - person Adam; 25.02.2013