Async в действии
От: WolfHound  
Дата: 20.05.10 13:24
Оценка: 112 (7)
http://code.google.com/p/nemerle/source/browse/nemerle/trunk/snippets/ComputationExpressions/AsyncHttp
Я добавил пример того как можно использовать async.
Данный код демонстрирует асинхронный запрос к нескольким сайтам.

    private button1_Click (_sender : object,  _e : System.EventArgs) : void
    {
      responceBox.Text = "";
      def receive(url)
      {
        comp async
        {
          def url = url.Trim();
          when (url != "")
          {
            def time = System.Diagnostics.Stopwatch.StartNew();
            try
            {
              defcomp content = HttpGet(url).Start();//Start без параметров запускает вычисление в общем пуле потоков.
              //defcomp прирывает исполнение и освобождает поток до тех пор пока вычисление не будет завершено.
              //после чего исполнение возобновляется в том же контексте исполнения.
              responceBox.Text += $"$url\nTime: $(time.Elapsed)\nContent-Length: $(content.Length)\n\n\n\n";
            }
            catch
            {
              | ex is Exception =>
                responceBox.Text += $"$url\nTime: $(time.Elapsed)\nException: $(ex.Message)\n\n\n\n";
            }
          }
        }
      }
      foreach (url in urlBox.Lines)
        _ = receive(url).Start(guiCtx);//Запуск в контексте gui.
    }


Собственно запрос страници.
    private HttpGet(url : string) : Async[string]
    {
      comp async
      {
        def req = WebRequest.Create(url);
        using (defcomp resp = req.AsyncGetResponse())//Асинхронно дожидаемся ответа от сервера.
        using (stream = resp.GetResponseStream())//Далее идет синхронное чтение ответа.
        using (reader = StreamReader(stream))
          return reader.ReadToEnd();
      }
    }

Если он при исполнении выбросит исключение то при попытке получить разультат будет выброшено это же исключение.
Это можно проверить введя не существующий url или не правильный протокол например htttp.

Пример того на склько легко превратить стандартный в .NET паттерн асинхронных вычислений в Async.
    public static AsyncGetResponse(this req : WebRequest) : Async[WebResponse]
    {
      Async.FromBeginEnd
        ( req.BeginGetResponse(_, null)
        , req.EndGetResponse(_)
        );
    }


Методы расширения для асинхронного чтения и записи в поток.
    public AsyncRead(this stream : IO.Stream, arr : array[byte], offset : int, numBytes : int) : Async[int]
    {
      Async.FromBeginEnd
        ( stream.BeginRead(arr, offset, numBytes, _, null)
        , stream.EndRead(_)
        );
    }

    public AsyncWrite(this stream : IO.Stream, arr : array[byte], offset : int, numBytes : int) : Async[FakeVoid]
    {
      Async.FromBeginEnd
        ( stream.BeginWrite(arr, offset, numBytes, _, null)
        , stream.EndWrite(_)
        );
    }

Их использование можно посмотреть в тестах
http://code.google.com/p/nemerle/source/browse/nemerle/trunk/snippets/ComputationExpressions/Test/AsyncTest.n
... << RSDN@Home 1.2.0 alpha 4 rev. 1305>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re: Async в действии
От: dsorokin Россия  
Дата: 20.05.10 16:35
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>http://code.google.com/p/nemerle/source/browse/nemerle/trunk/snippets/ComputationExpressions/AsyncHttp

WH>Я добавил пример того как можно использовать async.

Здорово! Выглядит интересно.

А у тебя как запускается само вычисление? Через Async[A].Start? Если да, то почему возвращается Async[A], а не извлеченное из вычисления значение типа A? Не могу разобраться. Просто я привык к тому шаблону, что запускалка обычно берет некое вычисление в монаде, запускает его, а затем возвращает что-то, но уже без монады в типе. То есть, происходит понижение типа.
Re[2]: Async в действии
От: WolfHound  
Дата: 20.05.10 17:20
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>Здорово! Выглядит интересно.


D>А у тебя как запускается само вычисление? Через Async[A].Start?

Да.
А результат получаем через defcomp в другом вычислении или через GetResult (надо будет добавить еще GetValue которое возвращает значение или выбрасывает исключение).

D>Если да, то почему возвращается Async[A], а не извлеченное из вычисления значение типа A? Не могу разобраться.

По тому что вычисление запускается асинхронно. И значения еще некоторое время не будет.
Так что возвращать нечего.

D>Просто я привык к тому шаблону, что запускалка обычно берет некое вычисление в монаде, запускает его, а затем возвращает что-то, но уже без монады в типе. То есть, происходит понижение типа.

Я просто делал систему так чтобы этим было удобно пользоваться.
В том же F# есть:
Start который ничего не возвращает
http://msdn.microsoft.com/en-us/library/ee353487%28v=VS.100%29.aspx
и есть StartAsTask который возвращает Task<'T>, а не значение. Ибо на момент вызова StartAsTask значения нет.
http://msdn.microsoft.com/en-us/library/ee821132%28v=VS.100%29.aspx

Я просто обобщил это дело и сделал вместо Task Async таким образом я могу ждать на этом самом Async внутри другого Async.
Причем я сделал так чтобы можно было в Async засунуть результат из другого потока. Ищи AsyncPromise в тестах.

Причем AsyncPromise можно и сам по себе использовать.
Весьма удобно для того чтобы передать данные из одного потока в другой.
... << RSDN@Home 1.2.0 alpha 4 rev. 1305>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[3]: Async в действии
От: dsorokin Россия  
Дата: 21.05.10 03:54
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>А результат получаем через defcomp в другом вычислении или через GetResult (надо будет добавить еще GetValue которое возвращает значение или выбрасывает исключение).


По идее defcomp может запускать ветку вычислений асинхронно сам по себе. Здесь отдельный вызов Start не нужен.

D>>Если да, то почему возвращается Async[A], а не извлеченное из вычисления значение типа A? Не могу разобраться.

WH>По тому что вычисление запускается асинхронно. И значения еще некоторое время не будет.
WH>Так что возвращать нечего.

Для этого есть defcomp. В f# все запускалки придерживаются описанного мною шаблона. Детали ниже.

D>>Просто я привык к тому шаблону, что запускалка обычно берет некое вычисление в монаде, запускает его, а затем возвращает что-то, но уже без монады в типе. То есть, происходит понижение типа.

WH>Я просто делал систему так чтобы этим было удобно пользоваться.

Хотелось бы, чтобы все эффекты максимально были отражены в системе типов.

WH>В том же F# есть:

WH>Start который ничего не возвращает
WH>http://msdn.microsoft.com/en-us/library/ee353487%28v=VS.100%29.aspx

Хм. Вполне соответствует по духу приведенному мною шаблону.

static member Start : Async<unit> * ?CancellationToken -> unit


Здесь берем некое вычисление значения типа unit в монаде Async ради получения побочного эффекта. Мы знаем точно, что есть только одно такое значение, а именно (). Вот его мы сразу и возвращаем. То, что сам побочный эффект получается асинхронно — это уже специфика запускалки. Все очень функционально, хотя и нечисто.

WH>и есть StartAsTask который возвращает Task<'T>, а не значение. Ибо на момент вызова StartAsTask значения нет.

WH>http://msdn.microsoft.com/en-us/library/ee821132%28v=VS.100%29.aspx

Этот пример тоже соответствует концепции.

static member StartAsTask : Async<'T> * ?TaskCreationOptions * ?CancellationToken -> Task<'T>


Здесь вспомни о монадных трансформерах. Можем думать так, что Task встроен как бы внутрь Async. При запуске вычисления в Async мы извлекаем этот Task, избавляясь от прибавки Async в типе. Происходит как бы понижение типа. Само значение Task<'T> ответственно теперь за конечное извлечение результата типа 'T.

WH>Я просто обобщил это дело и сделал вместо Task Async таким образом я могу ждать на этом самом Async внутри другого Async.

WH>Причем я сделал так чтобы можно было в Async засунуть результат из другого потока. Ищи AsyncPromise в тестах.

WH>Причем AsyncPromise можно и сам по себе использовать.

WH>Весьма удобно для того чтобы передать данные из одного потока в другой.

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

defcomp content = HttpGet(url).Start();


я бы просто написал

defcomp content = HttpGet(url);


или так с явным указанием пула

defcomp content = HttpGet(url).UsingDefaultPool();


WH>Я просто обобщил это дело и сделал вместо Task Async таким образом я могу ждать на этом самом Async внутри другого Async.

WH>Причем я сделал так чтобы можно было в Async засунуть результат из другого потока. Ищи AsyncPromise в тестах.

WH>Причем AsyncPromise можно и сам по себе использовать.

WH>Весьма удобно для того чтобы передать данные из одного потока в другой.

Если ты хочешь явно указывать в каком пуле следует запускать ветку асинхронного вычисления, то я думаю, что для этого нужно создать отличные от Start методы. Специальные методы вроде UsingDefaultPool с сигнатурой: Async[A] -> Async[A]. Саму же запускалку Start сделать такой как в f#. Не смешивать эти две разные по назначению вещи. В f# очень продуманная система со Start, RunSynchronously и Parallel. Мне кажется, что лучше оставить сигнатуры как у них.
Re[4]: Async в действии
От: dsorokin Россия  
Дата: 21.05.10 04:05
Оценка:
D>По идее defcomp может запускать ветку вычислений асинхронно сам по себе. Здесь отдельный вызов Start не нужен.

Конечно, достигается это не напрямую, а косвенно через вызовы типа Async.FromBeginEnd
Re: Async в действии
От: dsorokin Россия  
Дата: 21.05.10 05:32
Оценка:
2WolfHound

Кстати, ты знаешь, что у нас есть одно очень важное отличие в реализации вычислительных выражений по сравнению с f#?

Например, такая вещь

let m = async {
  while true do
    printfn "Hi!"
}


будет преобразована компилятором f# в

let m = 
  async.Delay (fun () ->
    async.While (fun () -> true,
      async.Delay (fun () -> 
        printfn "Hi!"
        async.Zero ())))


В нашем случае немерловский код будет значительно эффективнее:

def m =
  {
    while (true)
      WriteLine ("Hi!");
    async.Zero ()
  }


В этом есть и свои положительные стороны, и отрицательные. Положительное в том, что это гораздо эффективнее и хорошо сочетается с неизвестными макросами, которые подставляются как есть. Отрицательное в том, что стандартный while и While вычислительного выражения могут сильно отличаться семантически, что может привести к странным и неожиданным логическим ошибкам восприятия.
Re[2]: Async в действии
От: dsorokin Россия  
Дата: 21.05.10 05:48
Оценка:
D>В нашем случае немерловский код будет значительно эффективнее:

Если быть точным, то

def m =
  async.Delay (() =>
    {
      while (true)
        WriteLine ("Hi!");
      async.Zero ()
    })


не считая того, что async.Delay и async.Zero сами раскрываются у тебя как макросы.
Re[4]: Async в действии
От: WolfHound  
Дата: 21.05.10 08:55
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>По идее defcomp может запускать ветку вычислений асинхронно сам по себе. Здесь отдельный вызов Start не нужен.

defcomp ничего в отдельном потоке не запускает. Он тупо объеденяет вычисления.

D>Здесь вспомни о монадных трансформерах. Можем думать так, что Task встроен как бы внутрь Async. При запуске вычисления в Async мы извлекаем этот Task, избавляясь от прибавки Async в типе. Происходит как бы понижение типа. Само значение Task<'T> ответственно теперь за конечное извлечение результата типа 'T.

Если тебе так хочется понижение типа то для этого есть GetResult.

WH>>Причем AsyncPromise можно и сам по себе использовать.

WH>>Весьма удобно для того чтобы передать данные из одного потока в другой.
D>Но для этого есть defcomp. Он эту задачу может выполнять. Передавать данные из одного асинхронного потока в другой. То есть, вместо
Ты не понял. AsyncPromise можно вообще без ComputationExpressions использовать.
В одном потоке висим на GetResult, а в другом устанавляваем значение.

D>В f# очень продуманная система со Start, RunSynchronously и Parallel. Мне кажется, что лучше оставить сигнатуры как у них.

Я так не считаю.
У них там куча глупостей в интерфейсе.
Например StartWithContinuations в моей библиотеке просто не нужен.
Функцию RunSynchronously будет выполнять GetResult/GetValue.
...
Пойми простую вещь: Я не делаю кальку с F#. Я делают библиотеку которой удобно пользоваться.
... << RSDN@Home 1.2.0 alpha 4 rev. 1305>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[5]: Async в действии
От: dsorokin Россия  
Дата: 21.05.10 09:53
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Пойми простую вещь: Я не делаю кальку с F#. Я делают библиотеку которой удобно пользоваться.


Это не калька с f#. Это, вообще, идет от чистого хаскеля, где в обязательном порядке нужно указывать в сигнатуре функции все производимые ею эффекты. Там это обязаловка. В f# последовали их примеру при проектировании Async насколько это возможно. Я считаю, что создатели f# поступили очень грамотно. Только взглянув на описание функции, можно уже догадаться, что функция делает. У тебя же метод Start, стартующий вычисление и возвращающий опять же вычисление, совершенно неочевиден. Не должен Start быть в правой части defcomp. Впрочем, хозяин — барин.
Re[6]: Async в действии
От: WolfHound  
Дата: 21.05.10 11:04
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>У тебя же метод Start, стартующий вычисление и возвращающий опять же вычисление, совершенно неочевиден. Не должен Start быть в правой части defcomp. Впрочем, хозяин — барин.

Что тут не очевидного я не понимаю.
Start запускает операцию которая черт знает когда выполниться.
Обычно нам нужно дождаться ее выполнения и получить результат.
Что это за логика по которой я могу это сделалать в простом коде и не могу сделать в вычислении так чтобы не блокировать поток я не понимаю.
Короче из-за сферо-вакуумной идеологии я не буду ломать очень полезную функциональность.
Тем более что как я уже сказал для понижения типа есть GetResult.
... << RSDN@Home 1.2.0 alpha 4 rev. 1305>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[7]: Async в действии
От: dsorokin Россия  
Дата: 21.05.10 11:36
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Что тут не очевидного я не понимаю.

WH>Start запускает операцию которая черт знает когда выполниться.
WH>Обычно нам нужно дождаться ее выполнения и получить результат.
WH>Что это за логика по которой я могу это сделалать в простом коде и не могу сделать в вычислении так чтобы не блокировать поток я не понимаю.
WH>Короче из-за сферо-вакуумной идеологии я не буду ломать очень полезную функциональность.
WH>Тем более что как я уже сказал для понижения типа есть GetResult.

На разные операции должны быть разные функции. Инициация вычисления и запуск другой ветки внутри уже запущенного вычисления — это очень разные вещи. Для последнего есть defcomp по заранее подготовленным значениям, как я уже писал. К тому же, вот такая штука с подчеркиванием является плохим сигналом:

foreach (url in urlBox.Lines)
        _ = receive(url).Start(guiCtx);//Запуск в контексте gui.


Но ты — автор. Тебе и решать. Просто я стараюсь помочь тебе в силу своих умений. Постарайся понять меня правильно
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.