4

(.NET 3.5 ストックのみを使用できるため、タスクもリアクティブ拡張もありません)

単純なケースだと思っていたのですが、困惑しています。

簡単に言うと、BeginGetRequestStream の IAsyncResult を BeginMyOperation() の呼び出し元に返しています。EndGetRequestStream が呼び出されたときに呼び出される BeginGetResponse の IAsyncResult を実際に送り返したいのです。

だから私は疑問に思っています、どうすればいいですか

      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);
        }
4

5 に答える 5

3

Taskこれを解決する最も簡単な方法は、ラッパーを使用することだと思います。特に、完了TaskCompletionSource時にa を終了できますBeginGetResponseTask次に、そのために をTaskCompletionSource返すだけです。は をTask実装IAsyncResultしているため、クライアント コードを変更する必要はありません。

個人的に、私はさらに一歩進んでいきます:

  1. ( を使用して)BeginGetRequestStreamで囲みます。TaskFromAsync
  2. リクエストを処理し、(再び を使用して) にTaskラップする継続を作成します。BeginGetResponseTaskFromAsync
  3. Taskを完了するその秒の継続を作成しますTaskCompletionSource

IMHO、例外、および結果の値は、 よりTaskも sによってより自然に処理されIAsyncResultます。

于 2011-04-04T04:09:40.123 に答える
2

あなたがやろうとしていることは実行可能ですが、IAsyncResult の新しい実装を作成する必要があります (最初の IAsyncResult を監視し、2 番目の呼び出しを開始する "CompositeResult" のようなもの)。

ただし、このタスクは実際には 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
});
于 2011-04-04T04:00:28.060 に答える
2

この質問はほぼ 1 年前のものですが、asker の制約がまだ保持されている場合は、.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 13 つのステートメントで示されているように、開始される 3 つの非同期メソッドがあります。このパターンを使用すると、必要な数の非同期メソッドをチェーンしIAsyncResultて、呼び出し元に 1 つだけ返すことができます。

于 2012-04-16T01:17:49.543 に答える
1

何を達成しようとしているのかよくわかりませんが、コードを再考する必要があると思います。IAsyncResult インスタンスは、非同期メソッド呼び出しを処理できるようにするオブジェクトであり、 BeginXXXを介して非同期呼び出しを実行すると作成されます。

あなたの例では、基本的にまだ存在しないIAsyncResult のインスタンスを返したいと考えています。

あなたが解決しようとしている問題がどれなのかはよくわかりませんが、おそらくこれらのアプローチのいずれかがうまくいくでしょう:

  1. このコードをクラスにカプセル化し、イベントをサブスクライブすることで操作が完了したことをコードのユーザーに認識させます。
  2. このコードをクラスにカプセル化し、ユーザーが作業の完了時に呼び出されるコールバック デリゲートを提供するようにします。結果をパラメータとしてこのコールバックに渡すことができます

それが役に立てば幸い!

于 2011-04-04T03:29:34.237 に答える
0

まず、Jeffrey Richter の MSDN マガジンの記事「Implementing the CLR Asynchronous Programming Model (2007 年 3 月号)」から実装コードを入手AsyncResultNoResultします。AsyncResult<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 セクションを参照)。

非同期プログラミングは単純なものではありませんが、概念を理解すれば非常に強力です。

于 2011-04-23T16:45:20.550 に答える