2011-04-04 4 views
4

(можно использовать только с .NET 3.5 запас, так что никаких задач, не Реактивные Extensions)Ожидание метода IAsyncResult, что ждет на другом IAsyncResult (выстраивание)

У меня есть, что я думал, чтобы быть простой случай, но Я смущен этим.

Короткое этого является то, что я возвращаюсь 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); 
     } 
+1

@JonSkeet Я уверен, что JonSkeet мог ответить на этот вопрос .. вздох. –

ответ

1

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

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

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

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

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

+0

Хотите сохранить начальный/конечный шаблон, поскольку это в конечном счете будет использоваться в ASP.NET MVC. Спасибо хоть. –

+0

Фактически, использование асинхронного шаблона на основе событий (т. Е. С событием) довольно хорошо поддерживается ASP.NET MVC. –

2

То, что вы пытаетесь сделать, выполнимо, но вам нужно создать новую реализацию 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

Я не большой поклонник реактивных расширений ... но знаете ли вы, как создать IAsyncResult, который сделает это? –

3

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

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

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

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

+0

Невозможно использовать .NET 4 или любые дополнительные библиотеки. –

+1

@Guruprasad: Выполнение этого «вручную» довольно сложно. Обратите внимание, что Rx действительно включает backport всех функций 'Task' для .NET 3.5. –

+0

Мне нужно сделать это вручную, прежде всего потому, что я не могу использовать какие-либо вышеупомянутые методы сокращения или упрощения, представленные на этом вопросе. Я понимаю, мне нужно обернуть IAsyncResult в пользовательский, я просто не знаю, как это сделать. –

0

Прежде всего, получите код внедрения AsyncResultNoResult и AsyncResult<TResult> из статьи журнала MSDN Джеффри Рихтера «Implementing the CLR Asynchronous Programming Model».

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

// 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); 
      } 
     } 
    } 
} 

Некоторые вещи, чтобы отметить:

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

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

2

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

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

Например, чтобы опубликовать некоторые данные к 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. С помощью этого шаблона вы можете связать столько методов async, сколько захотите, и еще просто вернуть одному вызывающему абоненту IAsyncResult.

Смежные вопросы