【问题标题】:How can I easily chain two asynchronous requests together?如何轻松地将两个异步请求链接在一起?
【发布时间】:2009-03-19 01:41:02
【问题描述】:

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


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

    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() 的签名。

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


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

编译器团队的请求:

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

请使用用于以下目的的工具 这是他们的本意。如果你想 写状态机,写 自己设计的图书馆 专门解决那个一般 问题,然后使用它。

将工具用于其他目的 他们的目的是 “聪明”,聪明是坏的;聪明的是 维护程序员很难 明白,聪明难以扩展, 聪明很难推理,聪明 让人们思考“开箱即用”; 那个盒子里有好东西。


使用 Future&lt;&gt; 答案,因为它保留在 C# 中,这与我的示例代码相同。不幸的是,TPL 和 F# 都没有得到 Microsoft 的正式支持……目前。

【问题讨论】:

  • 你能提供这个2013 Edit: async and await now make this trivial! :-)的例子吗?我为此添加了 +1!

标签: c# .net multithreading asynchronous iasyncresult


【解决方案1】:

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

另一方面,如果您的情况在合理范围内,F# 会使此操作变得非常简单且易于正确创作。请参阅this video(即从 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

【讨论】:

    【解决方案2】:
     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&lt;T&gt; 的技术,如 http://blogs.msdn.com/pfxteam/archive/2008/02/29/7960146.aspx 所述

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

    【讨论】:

    • 是的!确切地。链式异步回调。当您发布时,我正在编写非常相似的代码。
    【解决方案3】:

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

    【讨论】:

    • blogs.msdn.com/oldnewthing/archive/2008/08/15/… "来自 [C#] 编译器团队的请求:虽然......你可以使用迭代器来实现......可怜的协程,......我希望人们不会...... . 将工具用于 [其他] 目的……是“聪明”,聪明是坏事;……”
    • 对 C# 编译器团队的恳求:那么,让 Async BeginXXX/EndXXX 模式不那么糟糕!就我个人而言,我支持 Brian - F# 是解决这类问题的方法。
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2016-03-30
      • 2016-06-11
      • 1970-01-01
      • 2012-12-07
      • 2019-07-22
      • 1970-01-01
      • 2011-03-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多