AsyncEnumeratorによる非同期操作のシンプル化

24528 ワード

ソースアドレス:http://www.cnblogs.com/taowen/archive/2008/11/03/1325837.html
AsyncEnumeratorによる非同期操作のシンプル化
前回はスレッド間でGUIにアクセスする方法について言及しました.この需要は往々にして非同期操作によるものである.今日はJeffrey Richterが書いたAsyncEnumeratorが非同期の問題を処理するのにどのように役立つかを見てみましょう.
まず、最も簡単な非同期ダウンロードページのコードを見てみましょう.
    public class Program

    {

        private static WebRequest request;



        public static void Main(string[] args)

        {

            request = WebRequest.Create("http://www.thoughtworks.com.cn");

            request.BeginGetResponse(HandleResponse, null);

            Console.ReadLine();

        }



        private static void HandleResponse(IAsyncResult ar)

        {

            request.EndGetResponse(ar);

            Console.WriteLine("Get response from web");

        }

    }


簡単じゃないですか.ダウンロード後にローカルディスクに非同期で保存する場合は、この時点では容易ではありません.
Code

コードが長すぎて、折り畳まなければなりません.このコードはまだ問題があります.異常を処理していないので、途中でエラーが発生しても、ファイルは閉じられません.
論理的にResponseを取得し、Responseストリームを読み出し、ローカルファイルストリームに書き込むのは実行順序から完了した後、しばらくして次の実行に続く.理論的には
HandleGetResponse(xxx);

while(NotFinished(xxx)) {

  HandleReadResponseStream(xxx);

  HandleWriteFileStream(xxx);

}

CleanUp();

しかし、私たちはそう書くことはできません.各操作の間には非同期で待機するプロセスがあるからです.実際には,非同期操作が1つの完了したフローを複数のコールバック関数に分散して完了させるためである.では、どのような方法で1つの方法を実行して、それからしばらく待って、もう1つの方法を実行することができますか?はい、これがyieldです.yieldは私が一時的に実行する権利を放棄することを代表して、IOが完成した後にあなたがまた私を実行して、私は次の操作をします.
        private static void Demo()

        {

            int i = 1;

            foreach (var fib in Fib())

            {

                Console.WriteLine(i + ": " + fib);

                if (i++ > 10)

                {

                    break;

                }

            }

        }



        private static IEnumerable<int> Fib()

        {

            int i = 1;

            int j = 1;

            yield return i;

            yield return j;

            while (true)

            {

                var k = i + j;

                yield return k;

                i = j;

                j = k;

            }

        }


この例では,Fib(フィボナッチ額列)はデッドサイクルである.もしそれが普通の関数であれば、あなたはそれを実行することはできません.それは永遠に実行権を放棄しないので、CPUを引っ張って究極のfibを計算します.しかし、私たちの例のFibはできません.yield returnのたびに関数が飛び出し、呼び出された場所に戻ります.そして呼び出すたびに、前回実行された場所から継続し、実行を継続するたびにすべてのローカル状態(ローカル変数の値)が前回の値を保持します.foreachの背後には、
            var enumerable = Fib();

            var enumerator = enumerable.GetEnumerator();

            enumerator.MoveNext(); //Fib    return i;

            Console.WriteLine(enumerator.Current);

            enumerator.MoveNext(); //Fib      return j;

            Console.WriteLine(enumerator.Current);

            enumerator.MoveNext(); //Fib      return i+j;

            Console.WriteLine(enumerator.Current);


 
上のIO操作シーケンスを少し書き換えるだけで、散らばらないようにすることができます.
BeginGetResponse(xxx);

yield return 1;

EndGetReponse(xxx);

while(NotFinished(xxx)) {

  BeginReadResponseStream(xxx);

  yield return 1;

  EndGetResponseStream(xxx);

  BeginWriteFileStream(xxx);

  yield return 1;

  EndGetResponseStream(xxx);

}

CleanUp();


毎回yield returnは実行権を放棄するので、この関数の外のどこかでBeginXXX操作のコールバックを待つことができ、IO操作が完了してからこの関数を実行し続けることができます.この考えに基づいて(最初はこの仁兄が考えたのですhttp://msmvps.com/blogs/mihailik/archive/2005/12/26/79813.aspx)、Jeffrey RichterはPower Threadingライブラリ(wintellectのダウンロードリンクが壊れていると書いてありますが、これを使ってhttp://www.wintellect.com/Downloads/PowerThreadingAttachments/Wintellect_Power_Threading_Library_(May_15,_2008).zip).最後のコードはこうです.
 
        private static IEnumerator<int> Download(AsyncEnumerator ae)

        {

            var webRequest = WebRequest.Create("http://www.thoughtworks.com.cn");

            webRequest.BeginGetResponse(ae.End(), null);

            yield return 1;

            var response = webRequest.EndGetResponse(ae.DequeueAsyncResult());

            Console.WriteLine("Get response from web");

            var buffer = new byte[4096];

            var count = buffer.Length;

            using (var responseStream = response.GetResponseStream())

            {

                using (var fileStream = new FileStream(@"c:\downloaded.html", FileMode.Create))

                {

                    while (count > 0)

                    {

                        Console.WriteLine("Read a chunk");

                        responseStream.BeginRead(buffer, 0, buffer.Length, ae.End(), null);

                        yield return 1;

                        count = responseStream.EndRead(ae.DequeueAsyncResult());

                        Console.WriteLine("Write a chunk");

                        fileStream.BeginWrite(buffer, 0, count, ae.End(), null);

                        yield return 1;

                        fileStream.EndWrite(ae.DequeueAsyncResult());

                    }

                }

            }

            Console.WriteLine("Finished downloading from response");

        }


簡単ではないでしょうか.しかし、もう一つの問題はyield returnです.私はこの関数を一時的に終了し、非同期操作が完了してから実行を続けるためであることを知っています.しかし、私が分からないのは、なぜyield return 1なのかということです.
 
実はこのyield return 1は別の高度な機能に使用されています.1つの非同期操作が終了するのを待って、私の行の後のコードを実行します.「.yield return 2の場合は、2つの非同期操作を待つことになります.だから、まずbeginの2つの非同期操作を行ってからyield return 2を待つ必要があります.AsyncEnumeratorには戻り値などの高度な機能があり、AsycnEnumeratorの内部には前述のAsyncOperationManagerが使用されているので、あなたのコードでGUIを安全に操作することができ、スレッド間の問題を恐れることはありません.
参考資料:
Asynchronous iterators:
http://msmvps.com/blogs/mihailik/archive/2005/12/26/79813.aspx
Simplified APM With The AsyncEnumerator:
http://msdn.microsoft.com/en-us/magazine/cc546608.aspx
Simplified APM with C#:
http://msdn.microsoft.com/en-us/magazine/cc163323.aspx
More AsyncEnumerator Features:
http://msdn.microsoft.com/en-us/magazine/cc721613.aspx
Using C# 2.0 iterators to simplify writing asynchronous code:
http://blogs.msdn.com/michen/archive/2006/03/30/using-c-2-0-iterators-to-simplify-writing-asynchronous-code.aspx
http://blogs.msdn.com/michen/archive/2006/04/01/using-c-2-0-iterators-to-simplify-writing-asynchronous-code-part-2.aspx
付記:
もしもし、ブロガーさん?なぜ新しいスレッドを直接作成しないで、そこで同期操作で上記の動作を完了しますか?この問題は私のところで等価なのになぜ使うのか.NETのAPM(Asynchronous Programming Model,非同期プログラミングモデル).正しい答えは、「http://msdn.microsoft.com/en-us/magazine/cc301191.aspxああ、Jeffrey Richterはこの問題の答えを書いたに違いない.それほど正確ではない答え:
1、非同期操作を実現するための柔軟性を提供し、新しいスレッドは多くの実現の一つにすぎない.
これにより、WindowsのOverlapped I/Oを利用することができますが、これはスレッドの問題にかかわらず、カーネルレベルのコールバックです.パフォーマンスはepollに直行します.
2、新しいスレッドをいつ使用するかの柔軟性を提供し、最初に新しいスレッドを作成し、すべてのコードをそこに置いて同期して実行するのはいずれかにすぎません.
人気のあるideaはSEDA(Staged Event Driven Architecture)と呼ばれ、その核心は長操作を非同期の短操作に分解し、異なるサイズのThread Poolで異なるタイプの非同期操作をコールバックし、スレッドのstage間の最適な配合比を最適化することである.これにより、リクエストがあると新しいスレッドが発生するオーバーヘッドが回避され、スレッドが多くなるとシステムが応答できなくなります.また,単一スレッド非同期コールバックの低リソース利用率,特にCPUがマルチコア化されている場合を回避した.APMとAsyncEnumeratorを利用して、自分で実現したThreadPoolを加えて、一つ作ります.NETバージョンのSEDAアーキテクチャも可能です.