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);
}
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:
- Wrap
BeginGetRequestStream
in aTask
(usingFromAsync
). - Create a continuation for that
Task
that processes the request and wrapsBeginGetResponse
in aTask
(again, usingFromAsync
). - Create a continuation for that second
Task
that completes theTaskCompletionSource
.
IMHO, exceptions and result values are more naturally handled by Task
s than IAsyncResult
.
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
});
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.
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:
- Encapsulate this code in a class, and make the users of your code aware that the operation is completed by subscribing to an event.
- 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!
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.
精彩评论