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

时间:2011-04-04 03:01:22

标签: c# .net asynchronous 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);
        }

5 个答案:

答案 0 :(得分:3)

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

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

  1. BeginGetRequestStream换成Task(使用FromAsync)。
  2. 为处理请求的Task创建续集,并在BeginGetResponse中包裹Task(再次使用FromAsync)。
  3. 为完成Task
  4. 的第二个TaskCompletionSource创建续集

    恕我直言,例外和结果值由TaskIAsyncResult更自然地处理。

答案 1 :(得分:2)

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

但是,使用Reactive Extensions实际上这个任务实际上要容易得多 - 在这种情况下,您可以使用Observable.FromAsyncPattern将Begin / End方法转换为返回IObservable的Func(表示异步结果),然后使用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
});

答案 2 :(得分:2)

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

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

例如,要将一些数据发布到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语句所示,有三种异步方法启动。使用此模式,您可以根据需要链接任意数量的异步方法,并且仍然只需向调用者返回一个IAsyncResult

答案 3 :(得分:1)

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

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

我真的不知道你要解决的问题是哪一个,但这些方法中的一种可能更适合你:

  1. 在一个类中封装此代码,并让代码用户通过订阅活动了解操作已完成。
  2. 在类中封装此代码,并使用户提供将在工作完成时调用的回调委托。您可以将结果作为参数传递给此回调
  3. 希望它有所帮助!

答案 4 :(得分:0)

首先,从Jeffrey Richter的MSDN杂志文章“Implementing the CLR Asynchronous Programming Model(2007年3月号)”中获取AsyncResultNoResultAsyncResult<TResult>实施代码。

拥有这些基类后,您可以相对轻松地实现自己的异步结果。在这个例子中,我将使用您的基本代码来启动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<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);
            }
        }
    }
}

有些注意事项:

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

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