Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Waiting on an IAsyncResult method that waits on another IAsyncResult (Chaining)

(can only use .NET 3.5 stock, so no Tasks, no Reactive Extensions)

I have, what I thought to be a simple case, but I'm baffled at it.

The short of it is that, I'm returning BeginGetRequestStream's IAsyncResult to the caller of BeginMyOperation(), and I want to really send back the IAsyncResult of BeginGetResponse, which is called when the EndGetRequestStream is called.

So I'm wondering, how do I

      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);
        }
like image 427
Guruprasad Nilam Avatar asked Feb 24 '23 15:02

Guruprasad Nilam


2 Answers

I think the easiest way to solve this is to use Task wrappers. In particular, you can finish a TaskCompletionSource when BeginGetResponse completes. Then just return the Task for that TaskCompletionSource. Note that Task implements IAsyncResult, so your client code won't have to change.

Personally, I would go a step further:

  1. Wrap BeginGetRequestStream in a Task (using FromAsync).
  2. Create a continuation for that Task that processes the request and wraps BeginGetResponse in a Task (again, using FromAsync).
  3. Create a continuation for that second Task that completes the TaskCompletionSource.

IMHO, exceptions and result values are more naturally handled by Tasks than IAsyncResult.

like image 61
Stephen Cleary Avatar answered Feb 27 '23 04:02

Stephen Cleary


The thing you're trying to do is doable, but you need to create a new implementation of IAsyncResult (something like "CompositeResult" that watches the first IAsyncResult, then kicks off the 2nd call).

However, this task is actually far easier using the Reactive Extensions - in that case you'd use Observable.FromAsyncPattern to convert your Begin/End methods into a Func that returns IObservable (which also represents an async result), then chain them using 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
});
like image 31
Ana Betts Avatar answered Feb 27 '23 05:02

Ana Betts