代码之家  ›  专栏  ›  技术社区  ›  Ðаn

如何轻松地将两个异步请求链接在一起?

  •  4
  • Ðаn  · 技术社区  · 15 年前

    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() ,执行regex匹配,然后调用 imageRequest.BeginGetResponse() . 同时使用标准异步调用模式并保留 BeginGetDilbert()) EngestDelBurt() .

    我试过几种方法,但都没有完全满意,这似乎是一种皇家的痛苦。因此,这个问题。-)


    编辑:似乎使用迭代器的方法是 frowned on by the C# compiler team .

    编译团队的请求:

    尽管可以肯定的是, 可以使用迭代器实现 国家机器,可怜的人协同作战, 等等,我希望人们不要这样做 所以。

    请使用工具 他们的意图。如果你想要 要写入状态机,请写入 你自己设计的图书馆 专门解决这个问题 问题,然后使用它。

    将工具用于除 他们的目的是 聪明是坏的;聪明是坏的 难以维护程序 明白了,聪明很难扩展, 聪明很难推理,聪明 让人们觉得“出格”; 那个盒子里有好东西。


    与之同行 Future<> 回答,因为它保持在C中,这与我的示例代码相同。不幸的是,无论是TPL还是F,都没有得到微软的官方支持。

    4 回复  |  直到 11 年前
        1
  •  3
  •   Mark Cidade    15 年前
     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()方法,可以使用 Future<T> 如上所述 http://blogs.msdn.com/pfxteam/archive/2008/02/29/7960146.aspx

    也见 http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse.aspx

        2
  •  4
  •   Brian    15 年前

    把这件事搞好有点像噩梦。您需要创建回调来传递到每个“begin”方法,然后运行该方法的“continue”。(别忘了确保所有异常处理和完成的同步逻辑都是正确的!)当你在今天的C中写下这篇文章时,你的代码会变成一堆毫无希望的意大利面,但这是你实现目标的唯一方法(不让线程阻塞I/O等待)。

    另一方面,如果这是你处境的原因, F# 使这非常简单和直接的作者正确。见 this video (也就是说,从52:20开始的8分钟)作为概要。

    编辑

    为了回答丹的评论,这里有一个非常粗略的草图…我从我在Outlook中写的一封电子邮件中提取了它,我怀疑它是编译的。异常路径总是粗糙的,所以要小心(如果抛出cb呢?);您可能希望在C_某处(我不知道在哪里,我确信一定有很多)找到一个可靠的ar/begin/end实现,并将其用作模型,但这显示了要点。问题是,一旦你写了这篇文章,你就会一直拥有它;beginrun和endrun在任何f异步对象上都作为“开始/结束”工作。我们在F bug数据库中有一个建议,在未来的F库版本中,在异步的基础上公开开始/结束APM,以便更容易地使用传统C代码的F异步计算。(当然,我们也在努力更好地使用.NET 4.0中并行任务库中的“task”。)

    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
    
        3
  •  2
  •   Joel Mueller    15 年前

    你可能会发现Jeff Richter的 AsyncEnumerator 简化了很多事情。你可以在电视上看到 PowerThreading 图书馆。

        4
  •  1
  •   Matt Davison    15 年前

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