Асинхронное сканирование F # - PullRequest
1 голос
/ 11 июня 2010

При сканировании веб-страниц мне нужно быть осторожным, чтобы не делать слишком много запросов к одному и тому же домену, например, я хочу поставить 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)

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

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

Спасибо !!

1 Ответ

4 голосов
/ 11 июня 2010

Я думаю, что вы хотите сделать следующее - создать 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 будутбегать до тех пор, пока они не спят, и так далее.И затем через одну секунду просыпается первый асинхронный сервер и отправляет запрос, а через секунду просыпается второй асинхронный ... так что это должно работать.1с является только приблизительным, так как они запускают свои таймеры, каждый из которых имеет крошечную частоту, расположенную в шахматном порядке друг от друга ... Вы можете захотеть немного ее буферизовать, например, 1100 мс или что-то, если нужное отключение действительно точново-вторых (задержки в сети и тому подобное все еще оставляют немного этого вне возможного контроля над вашей программой, вероятно).

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...