4

2013年編集: asyncそしてawait今、これを簡単にしましょう!:-)


Web サイトをスクリーン スクレイピングするコードをいくつか用意しました (説明のみを目的としています)。

    public System.Drawing.Image GetDilbert()
    {
        var dilbertUrl = new Uri(@"http://dilbert.com");
        var request = WebRequest.CreateDefault(dilbertUrl);
        string html;
        using (var webResponse = request.GetResponse())
        using (var receiveStream = webResponse.GetResponseStream())
        using (var readStream = new StreamReader(receiveStream, Encoding.UTF8))
            html = readStream.ReadToEnd();

        var regex = new Regex(@"dyn/str_strip/[0-9/]+/[0-9]*\.strip\.gif");
        var match = regex.Match(html);
        if (!match.Success) return null;
        string s = match.Value;
        var groups = match.Groups;
        if (groups.Count > 0)
            s = groups[groups.Count - 1].ToString();    // the last group is the one we care about

        var imageUrl = new Uri(dilbertUrl, s);
        var imageRequest = WebRequest.CreateDefault(imageUrl);
        using (var imageResponse = imageRequest.GetResponse())
        using (var imageStream = imageResponse.GetResponseStream())
        {
            System.Drawing.Image image_ = System.Drawing.Image.FromStream(imageStream, true /*useEmbeddedColorManagement*/, true /*validateImageData*/);
            return (System.Drawing.Image)image_.Clone(); // "You must keep the stream open for the lifetime of the Image."
        }
    }

ここで、GetDilbert() を非同期で呼び出したいと思います。デリゲートを使用する簡単な方法:

    Func<System.Drawing.Image> getDilbert;
    IAsyncResult BeginGetDilbert(AsyncCallback callback, object state)
    {
        getDilbert = GetDilbert;
        return getDilbert.BeginInvoke(callback, state);
    }
    System.Drawing.Image EndGetDilbert(IAsyncResult result)
    {
        return getDilbert.EndInvoke(result);
    }

これは確かに機能しますが、デリゲート スレッドが 2 つの I/O 操作の待機にほとんどの時間を費やすため、あまり効率的ではありません。

私がやりたいことは、 を呼び出しrequest.BeginGetResponse()、正規表現の一致を実行してから、 を呼び出すことimageRequest.BeginGetResponse()です。すべて標準の非同期呼び出しパターンを使用し、BeginGetDilbert()EndGetDilbert( ) の署名を保持します。

私はいくつかのアプローチを試しましたが、完全に満足できるものはありませんでした。これは王道の痛みのようです。したがって、質問です。:-)


編集: イテレータを使用するアプローチは、C# コンパイラ チームによって眉をひそめられているようです。

コンパイラ チームからのお願い:

イテレータを使用してステート マシンや貧弱なコルーチンなどを実装できるのは確かですが、そうしないでほしいと思います。

ツールは本来の目的のために使用してください。ステート マシンを作成する場合は、その一般的な問題を解決するために特別に設計されたライブラリを自分で作成し、それを使用します。

意図したもの以外の目的でツールを使用することは「賢い」ことであり、賢いことは悪いことです。巧妙さはメンテナンス プログラマーにとって理解するのが難しく、巧妙さは拡張しにくく、巧妙さについて推論するのは難しく、巧妙さは人々に「箱から出して」考えさせます。その箱の中に良いものが入っています。


Future<>それは私のサンプルコードと同じC#にとどまるため、答えに行きます。残念ながら、TPL も F# も Microsoft によって正式にサポートされていません...まだです。

4

4 に答える 4

4

これを正しくするのは一種の悪夢です。メソッドの「継続」を実行する各「Begin」メソッドに渡すコールバックを作成する必要があります。(そして、すべての例外処理と CompletedSynchronously ロジックが正しいことを確認することを忘れないでください!) 今日 C# でこれを作成すると、コードはどうしようもなくごちゃごちゃしたスパゲッティになってしまいますが、それが目標を達成できる唯一の方法です (スレッドが I/O 待機でブロックされないようにします)。

一方、状況に応じて、F#を使用すると、これを非常に単純かつ簡単に正しく作成できます。概要については、このビデオ(つまり、52:20 から始まる 8 分間) を参照してください。

編集

ダンのコメントに答えるために、ここに非常に大まかなスケッチがあります... Outlookで書いた電子メールから引っ張ってきました。コンパイルできるとは思えません。例外パスは常に危険なので注意してください ('cb' がスローされたらどうしますか?)。C# で堅実な AR/Begin/End の実装をどこかで見つけて (どこにあるかはわかりませんが、きっとたくさんあるはずです)、それをモデルとして使用することをお勧めしますが、これは要点を示しています。問題は、これを 1 回作成すると、それをずっと保持できるということです。BeginRun と EndRun は、任意の F# 非同期オブジェクトの「開始/終了」として機能します。従来の C# コードから F# 非同期計算をより簡単に使用できるようにするために、F# ライブラリの今後のリリースで非同期の先頭に Begin/End APM を公開するよう、F# バグ データベースに提案があります。(もちろん、.Net 4.0 の並列タスク ライブラリの「タスク」との連携も改善するよう努めています。)

type AR<’a>(o,mre,result) =
    member x.Data = result
    interface IAsyncResult with
        member x.AsyncState = o
        member x.AsyncWaitHandle = mre
        member x.CompletedSynchronously = false
        member x.IsCompleted = mre.IsSignalled

let BeginRun(a : Async<’a>, cb : AsyncCallback, o : obj) =
    let mre = new ManualResetEvent(false)
    let result = ref None
    let iar = new AR(o,mre,result) :> IAsyncResult
    let a2 = async { 
        try
            let! r = a
            result := Choice2_1(r)
        with e ->
            result := Choice2_2(e)
            mre.Signal()
            if cb <> null then 
                cb.Invoke(iar)
            return () 
    }
    Async.Spawn(a2)
    iar

let EndRun<’a>(iar) =
    match iar with
    | :? AR<’a> as ar -> 
        iar.AsyncWaitHandle.WaitOne()
        match !(ar.Data) with
        | Choice2_1(r) -> r
        | Choice2_2(e) -> raise e
于 2009-03-19T02:20:43.243 に答える
3
 public Image GetDilbert()
 {
     var   dilbertUrl  = new Uri(@"http://dilbert.com");
     var   request     = WebRequest.CreateDefault(dilbertUrl);
     var   webHandle   = new ManualResetEvent(false /* nonsignaled */);
     Image returnValue = null;

     request.BeginGetResponse(ar => 
     {  
          //inside AsynchCallBack method for request.BeginGetResponse()
          var response = (HttpWebResponse) request.EndGetResponse(ar); 

          string html;  
          using (var receiveStream = response.GetResponseStream())
          using (var readStream    = new StreamReader(  receiveStream
                                                      , Encoding.UTF8))
          {
             html = readStream.ReadToEnd();
          }

          var re=new Regex(@"dyn/str_strip/[0-9/]+/[0-9]*\.strip\.gif");
          var match=re.Match(html);

          var imgHandle = new ManualResetEvent(true /* signaled  */);

          if (match.Success) 
          {   
              imgHandle.Reset();              

              var groups = match.Groups;
              var s = (groups.Count>0) ?groups[groups.Count-1].ToString()
                                       :match.Value;
              var _uri   = new Uri(dilbertUrl, s);
              var imgReq = WebRequest.CreateDefault(_uri);

              imgReq.BeginGetResponse(ar2 => 
              {  var imageRsp= (HttpWebResponse)imgReq.EndGetResponse(ar2);

                 using (var imgStream=imageRsp.GetResponseStream())
                 { 
                    var im=(Image)Image.FromStream(imgStream,true,true);
                    returnValue = (Image) im.Clone();
                 }    

                 imgHandle.Set();           
              }, new object() /*state*/);
          }      

          imgHandle.WaitOne();
          webHandle.Set();  
     }, new object() /* state */);

     webHandle.WaitOne();  
     return returnValue;      
 }

Begin/EndGetDilbert() メソッドについては、 http:Future<T> //blogs.msdn.com/pfxteam/archive/2008/02/29/7960146.aspxで説明されている手法を使用できます。

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse.aspxも参照してください。

于 2009-03-19T03:44:29.220 に答える
1

疑いの余地はありません: Concurrency and Coordination Runtime を使用してください。上記の手法の多くを使用し、独自のコードをローリングするよりもコードをより簡潔にします。

于 2009-03-24T19:33:47.330 に答える