Как я могу легко объединить два асинхронных запроса? - PullRequest
4 голосов
/ 19 марта 2009

2013 Edit: 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);
    }

Хотя это, безусловно, работает, это не очень эффективно, поскольку поток делегата будет тратить большую часть своего времени на ожидание двух операций ввода-вывода.

Я хотел бы позвонить request.BeginGetResponse(), сопоставить регулярное выражение, а затем позвонить imageRequest.BeginGetResponse(). Все при использовании стандартного шаблона асинхронных вызовов и сохранении подписей BeginGetDilbert () и EndGetDilbert () .

Я пробовал несколько подходов и не был полностью удовлетворен ни одним из них; это похоже на королевскую боль. Отсюда и вопрос. : -)


РЕДАКТИРОВАТЬ: Кажется, что подходы, использующие итераторы, одобрены командой компилятора C # .

Заявление от команды компилятора:

Хотя, несомненно, дело в том, что Вы можете использовать итераторы для реализации государственные машины, сопрограммы бедняков, и так далее, я хотел бы, чтобы люди не делали так.

Пожалуйста, используйте инструменты для целей которые они были предназначены. Если ты хочешь писать конечные автоматы, писать сами библиотека, которая разработана специально для решения этого общего проблема, а затем использовать его.

Использование инструментов для других целей, кроме для чего они предназначены «умный», а умный - это плохо; умный трудно для обслуживания программистов понимаешь, умное сложно расширить, умный трудно рассуждать, умный заставляет людей думать «из коробки»; в этой коробке есть хорошие вещи.


Переходя к ответу Future<>, потому что он остается в C #, что совпадает с моим примером кода. К сожалению, ни TPL, ни F # официально не поддерживаются Microsoft ... пока.

Ответы [ 4 ]

4 голосов
/ 19 марта 2009

Это своего рода кошмар, чтобы понять это правильно. Вам необходимо создать обратные вызовы для передачи в каждый метод «Begin», который затем запускает «продолжение» метода. (И не забудьте убедиться, что вся логика обработки исключений и логики CompletedSynchronously правильная!) Когда вы сегодня пишете это в C #, ваш код превращается в безнадежный беспорядок спагетти, но это единственный способ, которым вы можете достичь своей цели отсутствует блокировка потоков при ожидании ввода / вывода).

С другой стороны, если это в пределах вашей ситуации, F # делает это очень простым и понятным для автора правильно. См. это видео (а именно, 8 минут, начиная с 52:20) для краткого обзора.

EDIT

чтобы ответить на комментарий Дэна, вот очень грубый набросок ... Я взял его из письма, написанного в outlook, сомневаюсь, что оно компилируется. Пути исключений всегда грубые, так что будьте осторожны (что делать, если выдает «cb»?); Вы можете захотеть найти надежную реализацию AR / Begin / End в C # где-нибудь (я не знаю, где, я уверен, что их должно быть много) и использовать ее в качестве модели, но это показывает суть. Дело в том, что, как только вы создадите это один раз, у вас есть это на все времена; BeginRun и EndRun работают как 'begin / end' для любого асинхронного объекта F #. В базе данных ошибок F # есть предложение представить APM Begin / End поверх асинхронного в будущем выпуске библиотеки F #, чтобы упростить использование асинхронных вычислений F # из традиционного кода C #. (И, конечно, мы стремимся работать лучше с «Задачами» из библиотеки параллельных задач в .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
3 голосов
/ 19 марта 2009
 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 голосов
/ 20 марта 2009

Возможно, вы обнаружите, что AsyncEnumerator Джеффа Рихтера несколько упрощает ситуацию. Вы можете получить его в библиотеке Wintellect PowerThreading .

1 голос
/ 24 марта 2009

Никаких вопросов по этому поводу: используйте Параллельное и координационное время выполнения . Он использует многие из вышеупомянутых методов и сделает ваш код более лаконичным, чем ваш собственный.

...