Тайм-аут в WebRequest с асинхронными рабочими процессами в стиле F # - PullRequest
4 голосов
/ 31 октября 2011

Для более широкого контекста, здесь мой код , который загружает список URL.

Мне кажется, что при использовании выборки URL в стиле use! response = request.AsyncGetResponse() нет хорошего способа обработки таймаутов в F #. У меня почти все работает, как хотелось бы (обработка ошибок и асинхронная загрузка запросов и ответов), за исключением проблемы, которая возникает, когда веб-сайту требуется много времени для ответа. Мой текущий код просто висит бесконечно. Я пробовал это на написанном мной PHP-скрипте, который ждет 300 секунд. Ждал все время.

Я нашел «решения» двух видов, оба из которых нежелательны.

AwaitIAsyncResult + BeginGetResponse

Как и ответ ildjarn на этот другой вопрос переполнения стека . Проблема в том, что если вы поставили в очередь много асинхронных запросов, некоторые из них искусственно блокируются на AwaitIAsyncResult. Другими словами, сделан запрос на выполнение запроса, но что-то закулисное блокирует вызов. Это приводит к преждевременному срабатыванию тайм-аута AwaitIAsyncResult при выполнении множества одновременных запросов. Я предполагаю, что это ограничение на количество запросов к одному домену или просто ограничение на общее количество запросов.

Чтобы поддержать мои подозрения, я написал небольшое приложение WPF, чтобы нарисовать временную шкалу, когда запросы, кажется, начинаются и заканчиваются. В моем коде, указанном выше, обратите внимание на запуск и останов таймера в строках 49 и 54 (вызывающая строка 10). Вот результирующее изображение временной шкалы .

Когда я перемещаю начало таймера после первоначального ответа (поэтому я только рассчитываю время загрузки содержимого), временная шкала выглядит намного более реалистичной . Обратите внимание, что это два отдельных запуска, но без изменения кода, кроме места запуска таймера. Вместо того, чтобы измерять startTime непосредственно перед use! response = request.AsyncGetResponse(), я получаю его непосредственно после.

Для дальнейшей поддержки моего заявления я сделал график с Fiddler2 . Вот результирующий график времени . Очевидно, что запросы не начинаются точно, когда я им говорю.

GetResponseStream в новой теме

Другими словами, синхронные запросы и вызовы загрузки выполняются во вторичном потоке. Это работает , так как GetResponseStream учитывает свойство Timeout объекта WebRequest. Но в процессе мы теряем все время ожидания, так как запрос находится на связи, и ответ еще не вернулся. Мы могли бы также написать это на C # ...;)

Вопросы

  • Это известная проблема?
  • Есть ли какое-нибудь хорошее решение, которое использует асинхронные рабочие процессы F # и все еще допускает тайм-ауты и обработки ошибок?
  • Если проблема действительно в том, что я делаю слишком много запросов одновременно, то будет ли лучшим способом ограничить количество запросов использование Semaphore(5, 5) или что-то подобное?
  • Дополнительный вопрос: если вы посмотрели мой код, можете ли вы увидеть какие-то глупости, которые я сделал и мог исправить?

Если вас что-то смущает, пожалуйста, дайте мне знать.

Ответы [ 2 ]

0 голосов
/ 22 ноября 2011

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

open System

type IParallelLimiter =
    abstract GetToken : unit -> Async<IDisposable>

type Message= 
    | GetToken of AsyncReplyChannel<IDisposable>
    | Release

let start count =
    let agent =
        MailboxProcessor.Start(fun inbox ->
            let newToken () =
                { new IDisposable with
                    member x.Dispose () = inbox.Post Release }

            let rec loop n = async {
                    let! msg = inbox.Scan <| function
                        | GetToken _ when n = 0 -> None
                        | msg -> async.Return msg |> Some

                    return!
                        match msg with
                        | Release ->
                            loop (n + 1)
                        | GetToken port ->
                            port.Reply <| newToken ()
                            loop (n - 1)
                }
            loop count)

    { new IParallelLimiter with
        member x.GetToken () =
            agent.PostAndAsyncReply GetToken}

let limiter = start 100;;

for _ in 0..1000 do
    async {
        use! token = limiter.GetToken ()
        Console.WriteLine "Sleeping..."
        do! Async.Sleep 3000
        Console.WriteLine "Releasing..."
    } |> Async.Start
0 голосов
/ 22 ноября 2011

AsyncGetResponse просто игнорирует любое опубликованное значение тайм-аута ... вот решение, которое мы только что приготовили:

open System
open System.IO
open System.Net

type Request = Request of WebRequest * AsyncReplyChannel<WebResponse>

let requestAgent =
    MailboxProcessor.Start <| fun inbox -> async {
            while true do
                let! (Request (req, port)) = inbox.Receive ()

                async {
                    try
                        let! resp = req.AsyncGetResponse ()
                        port.Reply resp
                    with
                    | ex -> sprintf "Exception in child %s\n%s" (ex.GetType().Name) ex.Message |> Console.WriteLine
                } |> Async.Start
        }

let getHTML url =
    async {
        try
            let req = "http://" + url |> WebRequest.Create
            try
                use! resp = requestAgent.PostAndAsyncReply ((fun chan -> Request (req, chan)), 1000)
                use str = resp.GetResponseStream ()
                use rdr = new StreamReader (str)
                return Some <| rdr.ReadToEnd ()
            with
            | :? System.TimeoutException ->
                req.Abort()
                Console.WriteLine "RequestAgent call timed out"
                return None
        with
        | ex ->
            sprintf "Exception in request %s\n\n%s" (ex.GetType().Name) ex.Message |> Console.WriteLine
            return None
    } |> Async.RunSynchronously;;

getHTML "www.grogogle.com"

т.е. Мы делегируем другому агенту и вызываем его, предоставляя асинхронный тайм-аут ... если мы не получим ответ от агента в течение указанного промежутка времени, мы отменяем запрос и продолжаем.

...