4

2013 年编辑: async现在await让这变得微不足道!:-)


我有一些屏幕抓取网站的代码(仅用于说明目的!)

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

虽然这确实有效,但它的效率并不高,因为委托线程将花费大部分时间等待两个 I/O 操作。

我想做的是打电话request.BeginGetResponse(),做正则表达式匹配,然后打电话imageRequest.BeginGetResponse()。同时使用标准异步调用模式并保留BeginGetDilbert()EndGetDilbert()的签名。

我尝试了几种方法,但对其中任何一种都不完全满意;这似乎是一种皇家的痛苦。因此,问题。:-)


编辑: C# 编译器团队似乎不赞成使用迭代器的方法。

编译器团队的请求:

虽然可以肯定的是,您可以使用迭代器来实现状态机、穷人协程等等,但我希望人们不要这样做。

请按照预期目的使用工具。如果您想编写状态机,请为自己编写一个专门用于解决该一般问题的库,然后使用它。

将工具用于预期目的之外的目的是“聪明的”,聪明是坏的;聪明让维护程序员难以理解,聪明难以扩展,聪明难以推理,聪明让人“开箱即用”;那个盒子里有好东西。


选择Future<>答案,因为它保留在 C# 中,这与我的示例代码相同。不幸的是,TPL 和 F# 都没有得到 Microsoft 的官方支持……但是。

4

4 回答 4

4

把这件事做好是一场噩梦。您需要创建回调以传递给每个“开始”方法,然后运行该方法的“继续”。(并且不要忘记确保所有异常处理和 CompletedSynchronously 逻辑都是正确的!)当您今天在 C# 中编写此代码时,您的代码会变成一团毫无希望的意大利面条,但这是您实现目标的唯一方法(没有线程阻塞 I/O 等待)。

另一方面,如果您的情况在合理范围内,F#会使其非常简单直接地正确创作。请参阅此视频(即从 52:20 开始的 8 分钟)了解概要。

编辑

回答丹的评论,这是一个非常粗略的草图......我从我在 Outlook 中写的一封电子邮件中提取了它,我怀疑它是否可以编译。异常路径总是很粗糙,所以要小心(如果 'cb' 抛出怎么办?);你可能想在 C# 的某个地方找到一个坚如磐石的 AR/Begin/End 实现(我不知道在哪里,我相信肯定有很多)并将其用作模型,但这显示了要点。问题是,一旦你创作了一次,你就永远拥有它;BeginRun 和 EndRun 作为任何 F# 异步对象的“开始/结束”工作。我们在 F# 错误数据库中提出了一项建议,即在 F# 库的未来版本中将 Begin/End APM 暴露在异步之上,以便更轻松地从传统 C# 代码中使用 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://blogs.msdn.com/pfxteam/archive/2008/02/29/7960146.aspxFuture<T>中所述的技术

另请参阅http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse.aspx

于 2009-03-19T03:44:29.220 回答
2

您可能会发现 Jeff Richter 的AsyncEnumerator简化了很多事情。您可以在 Wintellect PowerThreading库中获得它。

于 2009-03-19T22:59:17.830 回答
1

毫无疑问:使用Concurrency and Coordination Runtime。它使用了许多上面提到的技术,并且将使您的代码比滚动您自己的代码更简洁。

于 2009-03-24T19:33:47.330 回答