Ожидание метода IAsyncResult, ожидающего другого IAsyncResult (Цепочка) - PullRequest
4 голосов
/ 04 апреля 2011

(можно использовать только акции .NET 3.5, поэтому никаких задач, никаких реактивных расширений)

У меня есть, как мне показалось, простой случай, но я озадачен этим.

Суть в том, что я возвращаю IAsyncResult объекта BeginGetRequestStream вызывающей стороне BeginMyOperation () и хочу действительно отправить обратно IAsyncResult объекта BeginGetResponse, который вызывается, когда вызывается EndGetRequestStream.

Вот мне и интересно, как мне

      public IAsyncResult BeginMyOperation(...)
      {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(requestUri);
            webRequest.Method = "POST";

            // This is the part, that puzzles me. I don't want to send this IAsyncResult back.
            return webRequest.BeginGetRequestStream(this.UploadingStreamCallback, state);
       }

      // Only want this to be called when the EndGetResponse is ready.
      public void EndMyOperation(IAsyncResult ar)
      {

      }

      private IAsyncResult UploadingStreamCallback(IAsyncResult asyncResult)
      {
            using (var s = state.WebRequest.EndGetRequestStream(asyncResult))
            {
                using (var r = new BinaryReader(state.Request.RequestData))
                {
                    byte[] uploadBuffer = new byte[UploadBufferSize];
                    int bytesRead;
                    do
                    {
                        bytesRead = r.Read(uploadBuffer, 0, UploadBufferSize);

                        if (bytesRead > 0)
                        {
                            s.Write(uploadBuffer, 0, bytesRead);
                        }
                    }
                    while (bytesRead > 0);
                }
            }

            // I really want to return this IAsyncResult to the caller of BeginMyOperation
            return state.WebRequest.BeginGetResponse(new AsyncCallback(state.Callback), state);
        }

Ответы [ 5 ]

3 голосов
/ 04 апреля 2011

Я думаю, что самый простой способ решить эту проблему - использовать Task оболочки. В частности, вы можете закончить TaskCompletionSource, когда BeginGetResponse завершится. Затем просто верните Task для этого TaskCompletionSource. Обратите внимание, что Task реализует IAsyncResult, поэтому код вашего клиента не нужно будет менять.

Лично я бы пошел дальше:

  1. Обернуть BeginGetRequestStream в Task (используя FromAsync).
  2. Создайте продолжение для этого Task, который обрабатывает запрос и переносит BeginGetResponse в Task (опять же, используя FromAsync).
  3. Создайте продолжение для той секунды Task, которая завершает TaskCompletionSource.

ИМХО, исключения и значения результата более естественно обрабатываются на Task с, чем IAsyncResult.

2 голосов
/ 16 апреля 2012

Я понимаю, что этому вопросу уже почти год, но если ограничения спрашивающего остаются, в .NET 3.5 есть опция, позволяющая легко составлять асинхронные операции.Посмотрите на библиотеку PowerThreading Джеффа Рихтера .В пространстве имен Wintellect.PowerThreading.AsyncProgModel вы найдете несколько вариантов класса AsyncEnumerator, которые вы можете использовать с генераторами последовательностей для написания асинхронного кода, как если бы он был последовательным.

Суть в том, что вы пишетеваш асинхронный код является телом генератора последовательности, который возвращает IEnumerator<int>, и всякий раз, когда вы вызываете асинхронный метод, вы запускаете yield return с количеством ожидающих асинхронных операций.Библиотека обрабатывает кровавые подробности.

Например, чтобы опубликовать некоторые данные в URL и вернуть содержимое результата:

public IAsyncResult BeginPostData(string url, string content, AsyncCallback callback, object state)
{
    var ae = new AsyncEnumerator<string>();
    return ae.BeginExecute(PostData(ae, url, content), callback, state);
}

public string EndPostData(IAsyncResult result)
{
    var ae = AsyncEnumerator<string>.FromAsyncResult(result);
    return ae.EndExecute(result);
}

private IEnumerator<int> PostData(AsyncEnumerator<string> ae, string url, string content)
{
    var req = (HttpWebRequest)WebRequest.Create(url);
    req.Method = "POST";

    req.BeginGetRequestStream(ae.End(), null);
    yield return 1;

    using (var requestStream = req.EndGetRequestStream(ae.DequeAsyncResult()))
    {
        var bytes = Encoding.UTF8.GetBytes(content);
        requestStream.BeginWrite(bytes, 0, bytes.Length, ae.end(), null);
        yield return 1;

        requestStream.EndWrite(ae.DequeueAsyncResult());
    }

    req.BeginGetResponse(ae.End(), null);
    yield return 1;

    using (var response = req.EndGetResponse(ae.DequeueAsyncResult()))
    using (var responseStream = response.GetResponseStream())
    using (var reader = new StreamReader(responseStream))
    {
        ae.Result = reader.ReadToEnd();
    }
}

Как видите, приватный PostData()Метод отвечает за основную часть работы.Существует три запущенных асинхронных метода, о чем свидетельствуют три оператора yield return 1.С помощью этого шаблона вы можете связать столько асинхронных методов, сколько захотите, и при этом просто вернуть один IAsyncResult вызывающей стороне.

2 голосов
/ 04 апреля 2011

То, что вы пытаетесь сделать, выполнимо, но вам нужно создать новую реализацию IAsyncResult (что-то вроде «CompositeResult», который следит за первым IAsyncResult, а затем запускает второй вызов).

Однако эта задача на самом деле намного проще с помощью Reactive Extensions - в этом случае вы бы использовали Observable.FromAsyncPattern для преобразования ваших методов Begin / End в Func, который возвращает IObservable (который также представляет асинхронный результат), затем объединяет их в цепочку, используя SelectMany:

IObservable<Stream> GetRequestStream(string Url);
IObservable<bool> MyOperation(Stream stream);

GetRequestStream().SelectMany(x => MyOperation(x)).Subscribe(x => {
    // When everything is finished, this code will run
});
1 голос
/ 04 апреля 2011

Я не совсем понимаю, чего вы пытаетесь достичь, но я думаю, что вы должны переосмыслить код. Экземпляр IAsyncResult - это объект, который позволяет обрабатывать асинхронные вызовы методов, и они создаются при выполнении асинхронного вызова через BeginXXX .

В вашем примере вы в основном хотите вернуть экземпляр IAsyncResult, который еще не существует .

Я действительно не знаю, какую проблему вы пытаетесь решить, но, возможно, один из этих подходов вам подойдет:

  1. Инкапсулируйте этот код в классе и сообщите пользователям вашего кода, что операция завершена, подписавшись на событие .
  2. Инкапсулируйте этот код в классе и заставьте пользователей предоставлять делегата обратного вызова, который будет вызываться после завершения работы. Вы можете передать результаты в качестве параметра для этого обратного вызова

Надеюсь, это поможет!

0 голосов
/ 23 апреля 2011

Сначала получите код реализации AsyncResultNoResult и AsyncResult<TResult> из статьи Джеффри Рихтера в журнале MSDN « Реализация модели асинхронного программирования CLR (выпуск за март 2007 г.)».

Как только выИмея эти базовые классы, вы можете относительно легко реализовать свой собственный асинхронный результат.В этом примере я буду использовать ваш основной код для запуска веб-запроса, а затем получу ответ в виде одной асинхронной операции, состоящей из нескольких внутренних асинхронных операций.

// This is the class that implements the async operations that the caller will see
internal class MyClass
{
    public MyClass() { /* . . . */ }

    public IAsyncResult BeginMyOperation(Uri requestUri, AsyncCallback callback, object state)
    {
        return new MyOperationAsyncResult(this, requestUri, callback, state);
    }

    public WebResponse EndMyOperation(IAsyncResult result)
    {
        MyOperationAsyncResult asyncResult = (MyOperationAsyncResult)result;
        return asyncResult.EndInvoke();
    }

    private sealed class MyOperationAsyncResult : AsyncResult<WebResponse>
    {
        private readonly MyClass parent;
        private readonly HttpWebRequest webRequest;
        private bool everCompletedAsync;

        public MyOperationAsyncResult(MyClass parent, Uri requestUri, AsyncCallback callback, object state)
            : base(callback, state)
        {
            // Occasionally it is necessary to access the outer class instance from this inner
            // async result class.  This also ensures that the async result instance is rooted
            // to the parent and doesn't get garbage collected unexpectedly.
            this.parent = parent;

            // Start first async operation here
            this.webRequest = WebRequest.Create(requestUri);
            this.webRequest.Method = "POST";
            this.webRequest.BeginGetRequestStream(this.OnGetRequestStreamComplete, null);
        }

        private void SetCompletionStatus(IAsyncResult result)
        {
            // Check to see if we did not complete sync. If any async operation in
            // the chain completed asynchronously, it means we had to do a thread switch
            // and the callback is being invoked outside the starting thread.
            if (!result.CompletedSynchronously)
            {
                this.everCompletedAsync = true;
            }
        }

        private void OnGetRequestStreamComplete(IAsyncResult result)
        {
            this.SetCompletionStatus(result);
            Stream requestStream = null;
            try
            {
                stream = this.webRequest.EndGetRequestStream(result);
            }
            catch (WebException e)
            {
                // Cannot let exception bubble up here as we are on a callback thread;
                // in this case, complete the entire async result with an exception so
                // that the caller gets it back when they call EndXxx.
                this.SetAsCompleted(e, !this.everCompletedAsync);
            }

            if (requestStream != null)
            {
                this.WriteToRequestStream();
                this.StartGetResponse();
            }
        }

        private void WriteToRequestStream(Stream requestStream) { /* omitted */ }

        private void StartGetResponse()
        {
            try
            {
                this.webRequest.BeginGetResponse(this.OnGetResponseComplete, null);
            }
            catch (WebException e)
            {
                // As above, we cannot let this exception bubble up
                this.SetAsCompleted(e, !this.everCompletedAsync);
            }
        }

        private void OnGetResponseComplete(IAsyncResult result)
        {
            this.SetCompletionStatus(result);
            try
            {
                WebResponse response = this.webRequest.EndGetResponse(result);

                // At this point, we can complete the whole operation which
                // will invoke the callback passed in at the very beginning
                // in the constructor.
                this.SetAsCompleted(response, !this.everCompletedAsync);
            }
            catch (WebException e)
            {
                // As above, we cannot let this exception bubble up
                this.SetAsCompleted(e, !this.everCompletedAsync);
            }
        }
    }
}

Некоторые вещи, на которые следует обратить внимание:

  • Вы не можете вызвать исключение в контексте асинхронного обратного вызова.Вы сломаете свое приложение, потому что некому будет с ним справиться.Вместо этого всегда выполняйте асинхронную операцию с исключением.Это гарантирует, что вызывающая сторона увидит исключение при вызове EndXxx и сможет соответствующим образом обработать его.
  • Предположим, что все, что BeginXxx может выдать, также может быть вызвано из EndXxx.В приведенном выше примере предполагается, что WebException может произойти в любом случае.
  • Установка статуса «завершено синхронно» важна в случае, когда вызывающая сторона выполняет асинхронный цикл.Это сообщит звонящему, когда ему нужно вернуться из своего асинхронного обратного вызова, чтобы избежать «погружений в стек».Более подробную информацию об этом можно найти здесь в блоге Майкла Маручека « Асинхронное программирование в Indigo » (см. Раздел «Погружение в стеке»).

Асинхронное программирование - не самая простая вещь, но онаочень мощный, когда вы понимаете концепции.

...