Асинхронное сканирование F#

При сканировании веб-страниц мне нужно быть осторожным, чтобы не делать слишком много запросов к одному и тому же домену, например, я хочу поставить 1 с между запросами. Насколько я понимаю, важно время между запросами. Поэтому, чтобы ускорить работу, я хочу использовать асинхронные рабочие процессы в F #, идея которых заключается в том, чтобы делать запросы с интервалом в 1 секунду, но избегать блокировки в ожидании ответа на запрос.

let getHtmlPrimitiveAsyncTimer (uri : System.Uri) (timer:int) =
    async{

            let req =  (WebRequest.Create(uri)) :?> HttpWebRequest
            req.UserAgent<-"Mozilla"
            try 

                Thread.Sleep(timer)
                let! resp =    (req.AsyncGetResponse())
                Console.WriteLine(uri.AbsoluteUri+" got response")
                use stream = resp.GetResponseStream()
                use reader = new StreamReader(stream)
                let html = reader.ReadToEnd()
                return html
            with 
            | _ as ex -> return "Bad Link"
                 }

Затем я делаю что-то вроде:

let uri1 = System.Uri "http://rue89.com"
let timer = 1000
let jobs = [|for i in 1..10 -> getHtmlPrimitiveAsyncTimer uri1 timer|]

jobs
|> Array.mapi(fun i job -> Console.WriteLine("Starting job "+string i)
                               Async.StartAsTask(job).Result)

Это нормально? Я очень не уверен в двух вещах: - Работает ли Thread.Sleep для задержки запроса? - Является ли использование StartTask проблемой?

Я новичок (как вы могли заметить) в F# (на самом деле программирование в целом), и все, что связано с потоками, меня пугает :)

Спасибо !!


person jlezard    schedule 11.06.2010    source источник


Ответы (1)


Я думаю, что вы хотите сделать, это - создать 10 заданий, пронумерованных «n», каждое из которых начинается через «n» секунд, - запустить их все параллельно.

Примерно как

let makeAsync uri n = async {
    // create the request
    do! Async.Sleep(n * 1000)
    // AsyncGetResponse etc
    }

let a = [| for i in 1..10 -> makeAsync uri i |]
let results = a |> Async.Parallel |> Async.RunSynchronously

Обратите внимание, что, конечно, все они не начнутся именно сейчас, если, например. у вас есть 4-ядерная машина, 4 начнут работать очень скоро, но затем быстро выполнятся до Async.Sleep, после чего следующие 4 будут работать, пока не заснут, и так далее. И затем через одну секунду просыпается первый асинхронник и отправляет запрос, а еще через секунду просыпается 2-й асинхронник, ... так что это должно работать. 1s только приблизительны, так как они запускают свои таймеры каждый очень немного в шахматном порядке друг от друга... вы можете немного буферизовать его, например. 1100 мс или около того, если отсечка, которая вам нужна, действительно составляет ровно секунду (возможно, сетевые задержки и тому подобное все еще оставляют часть этого вне возможного контроля вашей программы).

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

Вам не нужен StartAsTask, если вы не хотите взаимодействовать с .NET Tasks или позже выполнить блокировку рандеву с результатом через .Result. Если вы просто хотите, чтобы все они запускались, а затем блокировались для сбора всех результатов в массиве, Async.Parallel прекрасно сделает этот параллелизм fork-join для вас. Если они просто собираются распечатать результаты, вы можете запустить и забыть через Async.Start, что приведет к падению результатов на пол.

(Альтернативной стратегией является использование агента в качестве дросселя. Отправляйте все HTTP-запросы одному агенту, где агент является логически однопоточным и находится в цикле, выполняя Async.Sleep за 1 с, а затем обрабатывая следующий запрос. Это хороший способ сделать универсальную дроссельную заслонку... если подумать, может быть достойным блога для меня.)

person Brian    schedule 11.06.2010
comment
Хе-хе! Очень очень приятно, спасибо. F# действительно великолепен, мне нравится, что Async.Parallel сделает это за вас :), для начинающих, таких как я, это позволяет вам беспокоиться о том, чтобы код был правильным. Спасибо ! - person jlezard; 11.06.2010
comment
Был достигнут прогресс: stackoverflow.com/questions/3023153/ - person jlezard; 11.06.2010
comment
На самом деле Брайан, я не уверен, что это решение работает для большого количества uri, или это так? - person jlezard; 11.06.2010
comment
Смотрите мой ответ там; там проблема отличается от проблемы здесь, так как здесь вы априори знаете все uris и запускаете все сразу, а там вы открываете новые uris во время работы программы. - person Brian; 11.06.2010
comment
Да, спасибо, что увидели, работаем над этим :) . Для этого поста не проблема запустить |›Asyn.Parallel|›Asyn.RunSynchronously на очень большой последовательности асинхронных элементов? - person jlezard; 11.06.2010
comment
Правильно; Я думаю, что это было бы хорошо для десятков тысяч элементов (хотя я на самом деле не пробовал); моя догадка заключается в том, что первым ограничением будет массив результатов (например, если вы сохраните html-строку страниц в массиве результатов, убедитесь, что вы не используете всю память процесса, хранящую все эти большие строки). - person Brian; 11.06.2010