【问题标题】:Waiting on an IAsyncResult method that waits on another IAsyncResult (Chaining)等待另一个 IAsyncResult 的 IAsyncResult 方法(链接)
【发布时间】:2011-04-04 03:01:21
【问题描述】:

(只能使用 .NET 3.5 stock,所以没有任务,没有响应式扩展)

我有,我认为这是一个简单的案例,但我对此感到困惑。

简而言之,我正在将 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);
        }

【问题讨论】:

  • @JonSkeet 我相信 JonSkeet 可以回答这个问题.. 叹息。

标签: c# .net asynchronous iasyncresult


【解决方案1】:

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

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

  1. BeginGetRequestStream 包装在Task 中(使用FromAsync)。
  2. 为处理请求并将BeginGetResponse 包装在Task 中的Task 创建一个延续(同样,使用FromAsync)。
  3. 为完成TaskCompletionSource 的第二个Task 创建一个延续。

恕我直言,Tasks 比 IAsyncResult 更自然地处理异常和结果值。

【讨论】:

  • 无法使用 .NET 4 或任何其他库。
  • @Guruprasad:“手工”操作非常复杂。请注意,Rx 确实包含 .NET 3.5 的所有 Task 功能的反向移植。
  • 我需要手动完成,主要是因为我不能使用任何上述关于这个问题的缩短或简化技术。我知道我需要将 IAsyncResult 包装成一个自定义的,我只是不知道该怎么做。
【解决方案2】:

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

但是,使用响应式扩展,这项任务实际上要容易得多 - 在这种情况下,您可以使用 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
});

【讨论】:

  • 我不是响应式扩展的忠实粉丝......但你知道创建 IAsyncResult 的方法吗?
【解决方案3】:

我意识到这个问题已经快一年了,但是如果提问者的限制仍然存在,那么 .NET 3.5 上有一个选项可以轻松组合异步操作。看看 Jeff Richter 的PowerThreading library。在Wintellect.PowerThreading.AsyncProgModel 命名空间中,您会发现AsyncEnumerator 类的几个变体,您可以将它们与序列生成器一起使用来编写异步代码,就好像它是顺序的一样。

它的要点是您将异步代码编写为返回IEnumerator&lt;int&gt;的序列生成器的主体,并且每当您调用异步方法时,您都会发出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 给调用者。

【讨论】:

    【解决方案4】:

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

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

    我真的不知道您要解决哪个问题,但也许其中一种方法更适合您:

    1. 将此代码封装在一个类中,并让您的代码的用户知道该操作是通过订阅事件完成的。
    2. 将此代码封装在一个类中,并让用户提供一个回调委托,该委托将在工作完成时调用。您可以将结果作为参数传递给此回调

    希望对你有帮助!

    【讨论】:

    • 希望保留 Begin/End 模式,因为这最终将用于 ASP.NET MVC。不过,谢谢。
    • 其实,使用基于事件的异步模式(即带有事件)已经被 ASP.NET MVC 很好地支持了。
    【解决方案5】:

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

    一旦有了这些基类,您就可以相对轻松地实现自己的异步结果。在此示例中,我将使用您的基本代码启动 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 部分)。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-27
      • 2011-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多