Как стать автором
Обновить
5
0

Пользователь

Отправить сообщение
Единственное место, где упоминается
— ок, чуть дальше в книге

1. Async<'a> values are essentially a way of writing “continuation-passing” or “callback” programs
explicitly

2. The use of let! executes this operation asynchronously and registers
a callback. When the callback is invoked, the value pixels is bound to the result of the operation,
and the remainder of the asynchronous workflow is executed.

Если таки да, то это верно лишь для т.н. SynchronizationContext по умолчанию
— так оно и есть. Для других контекстов юзайте SwitchToContext, SwitchToNewThread, AwaitTask…
В общем книга вам в руки.
Не возьмусь утверждать… но, лично мне не видно причин, по которым — пусть в даже async контексте — continuation _безусловно_ должен «превращаться» в callback. Вы уверены в своих словах? Это же достаточно просто проверить.
— отпринтим айди потока в разных точках асинка
let webRequest(url: string) = 
    async {
            printfn "Point 0 in thread %d" System.Threading.Thread.CurrentThread.ManagedThreadId
            let webReq = WebRequest.Create(url) :?> HttpWebRequest
            webReq.Method <- "GET"              
            use! response = Async.AwaitTask(webReq.GetResponseAsync())  

            printfn "Point 1 in thread %d" System.Threading.Thread.CurrentThread.ManagedThreadId
                //ожидания получения ответа асинхронно - последующий код есть callback, хотя выглядит как последовательное выполнение
            use responseStream = response.GetResponseStream()
            let! bytes = responseStream.AsyncRead(int response.ContentLength)

            printfn "Point 2 in thread %d" System.Threading.Thread.CurrentThread.ManagedThreadId
                //асинхронное ожидание результата с сети - последующий код тоже есть callback-ом, который вызовется по получении всех данных
            bytes 
            |> System.Text.Encoding.UTF8.GetString
            |> printfn "%A" 
        }
webRequest "http://ya.ru" |> Async.Start //пример запуска асинхронного вычисления в новом потоке


на моей машине результат:

Point 0 in thread 12
Point 1 in thread 11
Point 2 in thread 7

— разные потоки. Детальные пояснения если вам интересно почитайте в книге Дона Сайма — создателя языка.
В разделе Understanding Thread Hopping конкретно обьяснено почему continuation есть callback-ом. Сама регистрация последнего идет в методе AsyncBuilder.Bind (в вашем коде сверху).

Связано это со спецификой обработки «стандартной» Task. Там да… в зависимости от SynchronizationContext _может_ потребоваться исполнение continuation в том же самом потоке. Со всеми вытекающими, как говорится.
— async вычисление может быть запущено или продолжено в разных контекстах — смотрите Async модуль, методы SwitchToContext, SwitchToNewThread…
Правильно заметели — let! + do!… — F#-сахарок — пеабразуется в вызовы методов. Но для async монады continuation и будет callback-ом. Для других — нет. Все зависит от типа монады (отдельная статья для хабра). Но опять же, наш диалог сводится к разным трактовкам одних и тех же явлений (игру слов).
Ок. Как я понял из ваших слов Erlang не предоставляет синхронных методов при работе с агентами. Тоесть, что посылка сообщения через !, что получение сообщения — receive, есть неблокирующими операциями. F# предоставляет аналогичные методы Post и Receive у агента (MailboxProcessor). Но также есть такие методы, как PostAndReply, PortAndReplyAsync, TryPostAndReply, PostAndTryAsyncReply. Конечно Erlang-овых двух методов как базиса достаточно, но эти методы зачастую упрощают жизнь. PostAndReply — приводит к синхронному ожиданию — в методе создается обратный канал передачи данных (AsyncReplyChannel), поток лочится пока агент-получатель не пошлет в канал ответ. PortAndReplyAsync (xAsync) метод не лочит поток, а возвращает обьект типа Async, который вы можете запустить в паралельном потоке, либо (опять повторюсь) «асинхронно ожидать». Конкретно тут это означает то, что поток освобождается и отдается пулу потоков, код после вызова метода PortAndReplyAsync размещается в callback и регистрируется в пуле по факту получения ответа от агента-получателя. Возможно понятие «асинхронно ожидать» сильно привязано к пулу потоков и для NodeJs, например, трудно придумать его обьяснения. Для NodeJs идет стартовая регистрация callback-ов (в которых могут регистрироватся новые callback-ки), а после — ожидание событий и вызов этих callback-ов.

Уговорили на пример:
let webRequest(url: string) = 
    async {
            let webReq = WebRequest.Create(url) :?> HttpWebRequest
            webReq.Method <- "GET"              
            use! response = Async.AwaitTask(webReq.GetResponseAsync())  
                //ожидания получения ответа асинхронно - последующий код есть callback, хотя выглядит как последовательное выполнение
            use responseStream = response.GetResponseStream()
            let! bytes = responseStream.AsyncRead(int response.ContentLength)
                //асинхронное ожидание результата с сети - последующий код тоже есть callback-ом, который вызовется по получении всех данных
            bytes 
            |> System.Text.Encoding.UTF8.GetString
            |> printfn "%A" 
        }

webRequest "http://ya.ru" |> Async.Start //пример запуска асинхронного вычисления в новом потоке


Но, тогда вас не затруднит показать, как сообщение может ждать _синхронно_? :-)
— сообщение не может ждать синхронно. В данном плане «асинхронно ожидает» означает что пока сообщение находится в очереди, поток может делать свои операции, пока не дайдет до вызова все тех же SleepEx,… Интересно что в F# нет синхронного метода Receive, но в TPL Dataflow есть.

Если же рассматривать APC вообще, то вы забыли про _планировщик_, который, не зависимо от состояния потока, устроит ему волшебный пендель.
— если я его не упоминал, это не значит что я его забыл. Почему вы говорите что независимо от состояния потока? По доке, для юзер моде APC поток должен находится в alertable state, что б начать обрабатывать
APC функцию. Детальней можете обьяснить?
Вы всерьез сравниваете «обертку» вокруг, емнип, libev… которая без планировщика от слова совсем, с системой, в которой есть полноценный планировщик
— всерьез не сравнивал. Просто увидел подобие на NodeJs в вашем же объяснении. Все зависит от уровня деталей на котором вы концентрируетесь. Глубоко копаете. Поскольку с Erlang-ом опыт мал, то по вашему совету, замолкаю на эту тему.

Это замечательно, что вы взяли на себя труд почитать немного MSDN на предмет APC. Мне искренне жаль разрушать ваши иллюзии… и, возможно, я вас немного разочарую. Но, APC –!!! та-да!!! – ничего не _ждет_. Как бы вам этого не хотелось.
— то, чем мы с вами заниаемся обсуждая кто-кого ждет напоминает игру слов :). Может у нас разные понимания вопроса, конечно.

Если уж вам так хочется – то _ждет_ именно _поток_, в очередь которого мы помещаем объект APC. Причем, если вдруг выясняется, что поток таки ничего не ждет, то _планировщик_, при первой же возможности, ткнет его носом в очередь.
— когда мы размещаем объект APC в очереди он не будет обработан потоком пока поток не перейдет в извещающее состояние (alertable state). Это вам не ожидание обработки? А переходит он в такое состояние какраз по функциям SleepEx, SignalObjectAndWait,…
Или я упустил какую нибудь важную деталь?

Во-вторых, предлагаю взять то, что вам ближе, и показать уже наконец уважаемой аудитории это ваше «асинхронное ожидание» на примере. С подробным разбором для не сведущих: кто, где, чего и как _ждет_, если вас не затруднит. Заранее спасибо.
— это с удовольствием, но в последующих статьях. Если читали внимательно, это вступительная статья в серии. Конкретно далее пойдет про async computation expression в F#, MailboxProcessor и TPL Dataflow.
потоки также могут обмениваться сообщениями (через блокирующие очереди)
— BufferBlock в TPL Dataflow

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

эти сигналы точно соответствуют семафорам в многопоточном программировании.
— в каком смысле соответствуют? Семафо́р — объект, ограничивающий количество потоков, которые могут войти в заданный участок кода. Можете дать какие-нибудь ссылки по этому вопросу?
Erlang VM — в большинстве случаев — это простой однопоточный процесс ОС
— очень подобно к NodeJs. Так что не зря кажется. Erlang процессы легки в том, что виртуальная машина Erlang не создать ОС потока для каждого созданного процесса.

APC — функция, выполняющаяся асинхронно в контексте потока. Она розмещается в очереди потока и (вот наконецто это слово) ждет ряда событий (SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx, or WaitForSingleObjectEx), которые тоже описаны по линке. Там также описан механизм выполнения IO запроса. Пересказывать не буду, почитаете на MSDN.

пока упираются в наличие каких-то пулов и т.п. Как так? Вас самого это не смущает?
— нет, не смущает. Callback должен выполнится в определенном потоке. Конкретно если брать механиз MailboxProcessor-а в F# или TPL Dataflow, то они выполняються в потоках из пула. Если брать NodeJs или Erlang, то в контексте одного потока.
На счет терминологии — вы правы. Приму к сведенью в следующих статьях.
На счет пункта 2 и 3. В данном случае рассматриваются использование агентов не только в рамках компьютерной сети, но и в рамках одного процесса на одной машине. В данном случае использование агентов составляет конкуренцию использованию мьютексов, семафоров, критических секций и т.д для доступа к общему ресурсу, поскольку ресурс пренадлежит агенту (его потоку) и получить ресурс можно только через сообщения. Для распределенных систем (сетевого приложения) действительно существует только передача сообщений через сокеты. На основе этого подхода формируються более серьезные технологии – REST, SOAP сервисы.

Спасибо за поправку про receive expression.
По-поводу ожидания — именно в случае асинхронного ожидания, вычисление, тоесть callback, ожидает события извне (например получения данных с сети или диска). Но сам поток не ожидает этого события. Он отдаеться как свободный пулу для других вычислений.
По-поводу асинхронности и многопоточности — вообще разные вещи. Яркий пример — NodeJs — один поток, а все IO асинхронные по умолчанию. Но в F# вы можете создать async монаду, которую либо запустить в паралельном потоке, либо запустить асинхронно ожидать ответа.

Именно _такой_ проблемы в Erlang нет. Но, при этом асинхронные, скаже так, «методы» очень даже есть :-)
— мне кажется что тут ситуация скорее подобна к NodeJs. Есть одна очередь событий и вы просто регистрируете callback на события. receive expression скорее всего и регистрирует такой callback в Erlang. Но в общем в концепции могут быть как синхронные так и асинхронные методы
Я дико извиняюсь, но, вы вот это сейчас за Erlang сказали?!
— нет, а конкретней про F# и TPL Dataflow. C Erlang-ом глубоко не работал.
поподробней изложить про «ожидать асинхронно»?
— поток не лочиться, а отдаеться тредпулу — коллбек через IO complation ports. Возможно в Erlang-е нет такой проблемы и фактически он не нуждаеться в Async методах.
. Но, вообще-то там ещё замечательный receive «определен»
— если поток в тредпуле, то этот receive какраз может и заблочить его.
Если агентов много — это может привести к глобальному деадлоку системы.
Поэтому есть смысл ожидать асинхронно сообщение — на счет всяких xAsync.
оказывается на достаточно низком уровне
это не мешает даным примитивам составлять более серьезные библиотеки.
В часности OTP построена на Erlang-процессах — от них отказа не было.
На MailboxProcessor-ре достаточно легко реализовать свои агенты как независимые компоненты системы — обернуть их в класс если хотите.

требуется над этим какая-то обертка, «шаблоны проектирования»
— в функциональном программировании и с появлением дженериков шаблоны кардинально меняют свой вид (если вы про класику).

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность