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

感情迁移 提交于 2019-12-06 04:45:40

问题


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

回答1:


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.




回答2:


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



回答3:


I realize that this question is almost one year old, but if the constraints of the asker still hold, there is an option available on .NET 3.5 to easily compose asynchronous operations. Look at Jeff Richter's PowerThreading library. In the Wintellect.PowerThreading.AsyncProgModel namespace, you will find several variants of the AsyncEnumerator class, which you can use with sequence generators to write async code as if it were sequential.

The gist of it is that you write your async code as the body of a sequence generator that returns an IEnumerator<int>, and whenever you call an async method you issue a yield return with the number of async operations to wait for. The library handles the gory details.

For example, to post some data to a url and return the contents of the result:

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

As you can see, the private PostData() method is responsible for the bulk of the work. There are three async methods kicked off, as indicated by the three yield return 1 statements. With this pattern, you can chain as many async methods as you'd like and still just return one IAsyncResult to the caller.




回答4:


I don't really understand what are you trying to achieve, but I think you should be rethinking the code. An IAsyncResult instance is the object that allows to to handle asynchronous method calls, and they are created when you perform an async call through BeginXXX.

In your example, you basically want to return an instance of an IAsyncResult that it doesn't exist yet.

I don't really know which is the problem you are trying to solve, but maybe one of these approaches work better for you:

  1. Encapsulate this code in a class, and make the users of your code aware that the operation is completed by subscribing to an event.
  2. Encapsulate this code in a class, and make the users provide a callback delegate that will be called when the work is finished. You may pass the results as a parameter to this callback

Hope it helps!




回答5:


First, get the AsyncResultNoResult and AsyncResult<TResult> implementation code from Jeffrey Richter's MSDN magazine article "Implementing the CLR Asynchronous Programming Model (March 2007 issue)."

Once you have those base classes, you can relatively easily implement your own async result. In this example, I will use your basic code to start the web request and then get the response as a single async operation composed of multiple inner async operations.

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

Some things to note:

  • You cannot throw an exception in the context of an async callback. You will crash your application since there will be no one to handle it. Instead, always complete the async operation with an exception. This guarantees that the caller will see the exception on the EndXxx call and can then handle it appropriately.
  • Assume that whatever BeginXxx can throw is also possible to be thrown from EndXxx. The example above example assumes that WebException could happen in either case.
  • Setting the "completed synchronously" status is important in the case where a caller is doing an asynchronous loop. This will inform the caller when they need to return from their async callback in order to avoid "stack dives". More information on this is available here on Michael Marucheck's blog post "Asynchronous Programming in Indigo" (see the Stack Dive section).

Asynchronous programming is not the simplest thing but it is very powerful once you understand the concepts.



来源:https://stackoverflow.com/questions/5534193/waiting-on-an-iasyncresult-method-that-waits-on-another-iasyncresult-chaining

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!