簡體   English   中英

等待IAsyncResult方法等待另一個IAsyncResult(鏈接)

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

(只能使用.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創建一個延續。

恕我直言,異常和結果值更自然地處理Task總比IAsyncResult

我意識到這個問題差不多有一年了,但如果提問者的約束仍然存在,.NET 3.5上有一個選項可以輕松地組成異步操作。 看看Jeff Richter的PowerThreading庫 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返回給調用者。

你要做的事情是可行的,但你需要創建一個新的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
});

我真的不明白你想要實現什么,但我認為你應該重新考慮代碼。 IAsyncResult實例是允許處理異步方法調用的對象,它們是在通過BeginXXX執行異步調用時創建的。

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

我真的不知道你要解決的問題是哪一個,但也許這些方法中的一種對你來說效果更好:

  1. 將此代碼封裝在類中,並通過訂閱事件使代碼的用戶意識到操作已完成。
  2. 將此代碼封裝在類中,並使用戶提供將在工作完成時調用的回調委托。 您可以將結果作為參數傳遞給此回調

希望能幫助到你!

首先,從Jeffrey Richter的MSDN雜志文章“ 實現CLR異步編程模型 (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的博客文章“ Indigo中的異步編程 ”(參見Stack Dive部分)。

異步編程並不是最簡單的事情,但一旦理解了概念,它就會非常強大。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM