等待IAsyncResult方法等待另一个IAsyncResult(链接)

(只能使用.NET 3.5库存,因此没有任务,没有Reactive Extensions)

我有,我认为是一个简单的案例,但我很困惑。

缺点是,我将BeginGetRequestStream的IAsyncResult返回给BeginMyOperation()的调用者,我想真正发送回BeginGetResponse的IAsyncResult,它在调用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); } 

我认为解决这个问题的最简单方法是使用Task包装器。 特别是,您可以在BeginGetResponse完成时完成TaskCompletionSource 。 然后只返回TaskCompletionSourceTask 。 请注意, Task实现了IAsyncResult ,因此您的客户端代码不必更改。

就个人而言,我会更进一步:

  1. Task包装BeginGetRequestStream (使用FromAsync )。
  2. 为该Task创建一个继续,该Task处理请求并在Task包装BeginGetResponse (再次使用FromAsync )。
  3. 为完成TaskCompletionSource第二个Task创建延续。

恕我直言,exception和结果值由IAsyncResultIAsyncResult更自然地处理。

你要做的事情是可行的,但你需要创建一个新的IAsyncResult实现(比如“CompositeResult”,它会监视第一个IAsyncResult,然后启动第二个调用)。

但是,使用Reactive Extensions实际上这个任务实际上要容易得多 – 在这种情况下,您可以使用Observable.FromAsyncPattern将Begin / End方法转换为返回IObservable的Func( 代表异步结果),然后使用SelectMany链接它们:

 IObservable GetRequestStream(string Url); IObservable MyOperation(Stream stream); GetRequestStream().SelectMany(x => MyOperation(x)).Subscribe(x => { // When everything is finished, this code will run }); 

我意识到这个问题差不多有一年了,但如果提问者的约束仍然存在,.NET 3.5上有一个选项可以轻松地组成异步操作。 看看Jeff Richter的PowerThreading库 。 在Wintellect.PowerThreading.AsyncProgModel命名空间中,您将找到AsyncEnumerator类的几种变体,您可以将它们与序列生成器一起使用来编写异步代码,就好像它是顺序的一样。

它的要点是您将异步代码编写为返回IEnumerator的序列生成器的主体,并且每当调用异步方法时,您都会发出一个带有等待的异步操作数的yield return 。 图书馆处理血腥细节。

例如,要将一些数据发布到url并返回结果的内容:

 public IAsyncResult BeginPostData(string url, string content, AsyncCallback callback, object state) { var ae = new AsyncEnumerator(); return ae.BeginExecute(PostData(ae, url, content), callback, state); } public string EndPostData(IAsyncResult result) { var ae = AsyncEnumerator.FromAsyncResult(result); return ae.EndExecute(result); } private IEnumerator PostData(AsyncEnumerator 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返回给调用者。

我真的不明白你想要实现什么,但我认为你应该重新考虑代码。 IAsyncResult实例是允许处理异步方法调用的对象,它们是在通过BeginXXX执行异步调用时创建的。

在您的示例中,您基本上希望返回它尚不存在的IAsyncResult实例。

我真的不知道你要解决的问题是哪一个,但也许这些方法中的一种对你更有效:

  1. 将此代码封装在类中,并通过订阅事件使代码的用户意识到操作已完成。
  2. 将此代码封装在类中,并使用户提供将在工作完成时调用的回调委托。 您可以将结果作为参数传递给此回调

希望能帮助到你!

首先,从Jeffrey Richter的MSDN杂志文章“ 实现CLR异步编程模型 (2007年3月号)”中获取AsyncResultNoResultAsyncResult实现代码。

拥有这些基类后,您可以相对轻松地实现自己的异步结果。 在此示例中,我将使用您的基本代码来启动Web请求,然后将响应作为由多个内部异步操作组成的单个异步操作来获取。

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

有些事情需要注意:

  • 您不能在异步回调的上下文中抛出exception。 您将崩溃您的应用程序,因为没有人可以处理它。 相反,始终使用exception完成异步操作。 这可以保证调用者在EndXxx调用上看到exception,然后可以适当地处理它。
  • 假设无论BeginXxx可以抛出什么,也可以从EndXxx抛出。 上面的示例假设在任何一种情况下都可能发生WebException。
  • 在调用者进行异步循环的情况下,设置“已完成同步”状态非常重要。 这将通知调用者何时需要从异步回调返回以避免“堆栈潜水”。 有关这方面的更多信息,请参阅Michael Marucheck的博客文章“ Indigo中的异步编程 ”(参见Stack Dive部分)。

异步编程并不是最简单的事情,但一旦理解了概念,它就会非常强大。