Использование BeginInvoke / EndInvoke в многопоточном режиме. Как взаимодействуют AsyncCallback, AsyncWaitHandle и IsCompleted? - PullRequest
4 голосов
/ 02 января 2009

Ответ Андреаса Хубера на на этот вопрос дал мне идею реализовать Concurrent<T> с асинхронными делегатами вместо ThreadPool. Однако мне все труднее понять, что происходит, когда AsyncCallback передается BeginInvoke, особенно когда несколько потоков имеют доступ к IAsyncResult. К сожалению, этот случай, кажется, не рассматривается в MSDN или где-либо еще, где я мог найти. Более того, все статьи, которые я мог найти, были либо написаны до того, как стали доступны замыкания и дженерики, либо выглядели так. Есть несколько вопросов (и ответы, которые, я надеюсь, верны, но я готов разочароваться):

1) Будет ли иметь значение использование закрытия как AsyncCallback?
(Надеюсь, нет)
2) Если поток ожидает AsyncWaitHandle, будет ли он сигнализироваться
а) до начала обратного вызова или б) после его окончания?
(Надеюсь, б)
3) Во время обратного вызова, что вернет IsCompleted? Возможности, которые я вижу:
а) true; б) false; c) false до того, как обратный вызов вызовет EndInvoke, true после.
(Надеюсь, б или в)
4) Будет ли выброшен DisposedObjectException, если какой-то поток ожидает на AsyncWaitHandle после вызова EndInvoke?
(Надеюсь, что нет, но я ожидаю, что да).

При условии, что ответы, как я надеюсь, похоже, что это должно работать:

public class Concurrent<T> { 
    private IAsyncResult _asyncResult;
    private T _result;

    public Concurrent(Func<T> f) { // Assume f doesn't throw exceptions
        _asyncResult = f.BeginInvoke(
                           asyncResult => {
                               // Assume assignment of T is atomic
                               _result = f.EndInvoke(asyncResult); 
                           }, null);
    }

    public T Result {
        get {
            if (!_asyncResult.IsCompleted)
                // Is there a race condition here?
                _asyncResult.AsyncWaitHandle.WaitOne();
            return _result;  // Assume reading of T is atomic
        }
    ...

Если ответы на вопросы 1-3 - это те, на которые я надеюсь, то здесь, насколько я вижу, не должно быть условий гонки.

Ответы [ 2 ]

2 голосов
/ 02 сентября 2009

Я недавно искал асинхронные вызовы. Я нашел указатель на статью с примером реализации IAsyncResult уважаемого автора Джеффри Рихтера. Изучив эту реализацию, я многое узнал о том, как работают асинхронные вызовы.

Вы также можете посмотреть, можете ли вы скачать и изучить исходный код для System.Runtime.Remoting.Messaging.AsyncResult, который вас особенно интересует. Вот ссылка на инструкции о том, как это сделать в Visual Studio .

Чтобы добавить немного к хорошим ответам JaredPar ...

1: Я полагаю, что если вы определите замыкание, которое может быть присвоено переменной типа AsyncCallback (принимает IAsyncResult и возвращает void), оно должно работать так, как вы ожидаете, что замыкание будет работать в качестве этого делегата, но я не уверен, что могут быть проблемы с объемом. Исходная локальная область должна быть возвращена задолго до того, как будет вызван обратный вызов (именно это делает ее асинхронной операцией), так что имейте это в виду в отношении ссылок на локальные (стековые) переменные и их поведения. Я думаю, что ссылки на переменные-члены должны быть хорошими.

2: Я думаю из вашего комментария, что вы, возможно, неправильно поняли ответ на этот. В примере реализации Джеффри Рихтера дескриптор ожидания сигнализируется до обратного вызова. Если вы думаете об этом, это должно быть так. Как только он вызывает обратный вызов, он теряет контроль над выполнением. Предположим, что метод обратного вызова выдает исключение .... выполнение может откатиться назад за метод, который вызвал обратный вызов, и, таким образом, предотвратить последующий сигнал дескриптора ожидания! Таким образом, дескриптор ожидания должен быть сигнализирован перед вызовом обратного вызова. Они также намного ближе по времени, если выполняются в таком порядке, чем если бы они сигнализировали о дескрипторе ожидания только после возврата обратного вызова.

3: Как говорит JaredPar, IsCompleted должен возвращать true до обратного вызова и до того, как будет обработан дескриптор ожидания. Это имеет смысл, потому что если IsCompleted имеет значение false, вы ожидаете, что вызов к EndInvoke будет блокироваться, и весь смысл дескриптора ожидания (как с обратным вызовом) должен знать, когда результат готов, а не будет блок. Итак, сначала IsCompleted устанавливается на true, затем сигнализируется дескриптор ожидания, а затем вызывается обратный вызов. Посмотрите, как пример Джеффри Рихтера делает это. Однако , вам, вероятно, следует избегать предположений о порядке, в котором эти три метода (опрос, дескриптор ожидания, обратный вызов) могут обнаружить завершение, поскольку их можно реализовать в другом порядке, чем ожидалось.

4: Я не могу вам помочь, за исключением того, что вы можете найти ответ, отладив исходный код фреймворка для реализации, которая вас интересует. Или, возможно, вы могли бы придумать эксперимент, чтобы выяснить ... или организовать хороший эксперимент и отладить исходный код фреймворка, чтобы быть действительно уверенным.

2 голосов
/ 02 января 2009

Вопрос 1

Я думаю, что часть проблемы - заблуждение. IAsyncResult не доступен из нескольких потоков, если вы явно не передаете его одному. Если вы посмотрите на реализацию API стиля Mos Begin *** в BCL, вы заметите, что IAsyncResult только когда-либо создается и уничтожается из потока, где фактически происходят вызовы Begin *** или End ***.

Вопрос 2

AsyncWaitHandle должен сигнализироваться после завершения операции на 100%.

Вопрос 3

IsCompleted должен возвращать true после завершения основной операции (больше не нужно делать). Лучший способ просмотреть IsComplete - если значение равно

  1. true -> Calling End *** немедленно вернется
  2. false -> Callind End *** блокируется на некоторый период времени

Вопрос 4

Это зависит от реализации. Здесь нет никакого способа дать общий ответ.

Образцы

Если вас интересует API, который позволяет легко запускать делегат в другом потоке и получать доступ к результату по завершении, ознакомьтесь с моей RantPack Utility Library . Он доступен в исходном и двоичном виде. Он имеет полностью разработанный Future API, который позволяет одновременно выполнять делегаты.

Кроме того, есть реализация IAsyncResult, которая охватывает большинство вопросов в этом посте.

...