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

时间:2009-03-19 01:41:02

标签: c# .net multithreading asynchronous iasyncresult

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<>答案,因为它保留在C#中,与我的示例代码相同。不幸的是,TPL和F#都没有得到微软的官方支持......但是。

4 个答案:

答案 0 :(得分:4)

实现这一目标真是一场噩梦。您需要创建回调以传递到每个'Begin'方法,然后运行方法的'continuation'。 (并且不要忘记确保所有异常处理和CompletedSynchronously逻辑都是正确的!)当你今天在C#中创建它时,你的代码变成了一堆无望的意大利面,但这是你实现目标的唯一方法(没有线程在I / O等待时阻塞。)

另一方面,如果它符合你的情况,F#使得这一点非常简单直接。有关概要,请参阅this video(即从52:20开始的8分钟)。

修改

回答Dan的评论,这是一个非常粗略的草图......我从我在outlook中写的一封电子邮件中删除它,我怀疑它是否编译。异常路径总是很粗糙,所以要小心(如果'cb'抛出怎么办?);你可能想在C#某处找到坚如磐石的AR / Begin / End实现(我不知道哪里,我肯定必须有很多)并将它用作模型,但这显示了要点。问题是,一旦你创作了一次,你就会一直拥有它; BeginRun和EndRun在任何F#异步对象上作为“开始/结束”工作。我们在F#bug数据库中建议在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

答案 1 :(得分: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.aspx

中描述的Future<T>技术

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

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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