简体   繁体   English

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

[英]Waiting on an IAsyncResult method that waits on another IAsyncResult (Chaining)

(can only use .NET 3.5 stock, so no Tasks, no Reactive Extensions) (只能使用.NET 3.5库存,所以没有任务,没有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. 缺点是,我将BeginGetRequestStream的IAsyncResult返回给BeginMyOperation()的调用者,我想真正发送回BeginGetResponse的IAsyncResult,它在调用EndGetRequestStream时调用。

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. 我认为解决这个问题的最简单方法是使用Task包装器。 In particular, you can finish a TaskCompletionSource when BeginGetResponse completes. 特别是,您可以在BeginGetResponse完成时完成TaskCompletionSource Then just return the Task for that TaskCompletionSource . 然后只返回TaskCompletionSourceTask Note that Task implements IAsyncResult , so your client code won't have to change. 请注意, Task实现了IAsyncResult ,因此您的客户端代码不必更改。

Personally, I would go a step further: 就个人而言,我会更进一步:

  1. Wrap BeginGetRequestStream in a Task (using FromAsync ). Task包装BeginGetRequestStream (使用FromAsync )。
  2. Create a continuation for that Task that processes the request and wraps BeginGetResponse in a Task (again, using FromAsync ). 为该Task创建一个继续,该Task处理请求并在Task包装BeginGetResponse (同样,使用FromAsync )。
  3. Create a continuation for that second Task that completes the TaskCompletionSource . 为完成TaskCompletionSource第二个Task创建一个延续。

IMHO, exceptions and result values are more naturally handled by Task s than IAsyncResult . 恕我直言,异常和结果值更自然地处理Task总比IAsyncResult

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

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. 它的要点是您将异步代码编写为返回IEnumerator<int>的序列生成器的主体,并且每当调用异步方法时,您都会发出一个带有等待的异步操作数的yield return The library handles the gory details. 图书馆处理血腥细节。

For example, to post some data to a url and return the contents of the result: 例如,要将一些数据发布到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();
    }
}

As you can see, the private PostData() method is responsible for the bulk of the work. 如您所见,私有PostData()方法负责大部分工作。 There are three async methods kicked off, as indicated by the three yield return 1 statements. 有三种异步方法启动,如三个yield return 1语句所示。 With this pattern, you can chain as many async methods as you'd like and still just return one IAsyncResult to the caller. 使用此模式,您可以根据需要链接尽可能多的异步方法,并且仍然只将一个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). 你要做的事情是可行的,但你需要创建一个新的IAsyncResult实现(类似“CompositeResult”,它监视第一个IAsyncResult,然后启动第二个调用)。

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: 但是,使用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
});

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 . IAsyncResult实例是允许处理异步方法调用的对象,它们是在通过BeginXXX执行异步调用时创建的。

In your example, you basically want to return an instance of an IAsyncResult that it doesn't exist yet . 在您的示例中,您基本上希望返回它尚不存在的IAsyncResult实例。

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! 希望能帮助到你!

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)." 首先,从Jeffrey Richter的MSDN杂志文章“ 实现CLR异步编程模型 (2007年3月号)”中获取AsyncResultNoResultAsyncResult<TResult>实现代码。

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. 在此示例中,我将使用您的基本代码来启动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);
            }
        }
    }
}

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. 这可以保证调用者在EndXxx调用上看到异常,然后可以适当地处理它。
  • Assume that whatever BeginXxx can throw is also possible to be thrown from EndXxx. 假设无论BeginXxx可以抛出什么,也可以从EndXxx抛出。 The example above example assumes that WebException could happen in either case. 上面的示例假设在任何一种情况下都可能发生WebException。
  • 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). 有关这方面的更多信息,请参阅Michael Marucheck的博客文章“ Indigo中的异步编程 ”(参见Stack Dive部分)。

Asynchronous programming is not the simplest thing but it is very powerful once you understand the concepts. 异步编程并不是最简单的事情,但一旦理解了概念,它就会非常强大。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM