1 (изменено: Drugoy, 2016-01-02 19:54:30)

Тема: AHK: Асинхронная отправка нескольких запросов и получение ответов

Есть скрипт, с примерно такой логикой (абстракция):
Пользователь отправляет n веб-запросов на веб-сервер (imgur), сервер их какое-то время обрабатывает, а потом отсылает ответы. Текстовые ответы надо поймать и (распарсенные) сохранить в буфер обмена.
Хочу, чтоб это делалось асинхронно, т.е. чтобы для отправки второго запроса не требовалось дождаться ответа на первый запрос.

Помогите разобраться с тем, как лучше запрограммировать такую логику.

У меня в скрипте с синхронной работой отправка производится так:
1. есть массив/объект с запросами для отправки.
2. этот объект передаётся аргументом к функции.
3. функция сначала один раз создаёт объект, к которому привязывается ComObject("WinHttp.WinHttpRequest.5.1")
4. затем функция циклически (for key in argObj) перебирает объект в аргументе, каждый раз выполняя:

httpObj.Open("POST", url) ; открываем форму для запроса
httpObj.Send(request%k%) ; отправка данных (каждый раз разные) из перебираемого объекта.
clipboard := clipboard . httpObj.ResponseText ; собираем в переменную ответ сервера

5. с последним запросом функция завершает свою работу

С переходом на асинхронный подход возникли проблемы: нагуглился такой пример асинхронной работы.
Т.е. после отправки первого запроса выставляется таймер на однократное выполнение метки (label) (которая начинается с HttpObj1.WaitForResponse() и тем самым сначала ждёт ответа от сервера, а дальнейшие команды выполняются только после его получения).
Сразу после того, как таймер был выставлен - скрипт продолжает свою работу и переходит к следующему запросу, который аналогичным образом выставляет ещё один таймер на однократное выполнение уже другой метки.

Такое решение мне подходит, т.к. получается, что если надо выполнить очередь из 20 асинхронных запросов к серверу и потом собрать 20 ответов на них, то нам надо создавать 20 отдельных объектов, 20 меток (и потом ещё как-то скоординировать их дальнейшую работу, ведь по условием задачи - нам надо всё записать в одну переменную и её содержимое запихнуть в clipboard, а ещё ведь может захотеться и пользователю как-то сообщать о прогрессе выполнения задачи).
Мне не хочется плодить метки (т.к. подразумевается, что запросов может быть аж до 255шт) и не хотелось бы плодить объекты без необходимости (т.е. если это необходимо - то ладно).
Как же правильно решить такую проблему?

И т.к. я до этого ни разу не работал с асинхронностью, то сразу сопутствующий вопрос: есть какие-то писанные/неписанные стандарты о том, какой должна быть пауза между запросами и какая скорость запросов обычно считается приемлемой? или это всё сильно зависит от сервера и лучше узнать опытным путём?

2

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

Объекты, думаю, для каждого запроса надо создавать новые.
А вот от меток, наверное, можно избавиться, там по ссылке упоминается:

With SetTimerF() you could make this even cleaner because you would only need one timer.

https://autohotkey.com/board/topic/5949 … /?p=670275
Но у меня не получается:

loop 5
{
   HttpObj%A_Index% := ComObjCreate("WinHttp.WinHttpRequest.5.1")
   HttpObj%A_Index%.Open("GET","http://forum.script-coding.com/viewforum.php?id=13&p=" A_Index,true)
   HttpObj%A_Index%.Send()
   SetTimerF("func",-1,Object(1, A_Index))
}
loop 5
{
   n := A_Index
   loop
   {
      if (text%n% != "")
         break 
   }
}
msgbox, done
Return

func(p){
global
HttpObj%p%.WaitForResponse()
text%p% := HttpObj%p%.ResponseText
}


SetTimerF( Function, Period=0, ParmObject=0, Priority=0 ) { 
 Static current,tmrs:=[] ;current will hold timer that is currently running
 If IsFunc( Function ) {
    if IsObject(tmr:=tmrs[Function]) ;destroy timer before creating a new one
       ret := DllCall( "KillTimer", UInt,0, PTR, tmr.tmr)
       , DllCall("GlobalFree", PTR, tmr.CBA)
       , tmrs.Remove(Function) 
    if (Period = 0 || Period = "off")
       return ret ;Return as we want to turn off timer
	 ; create object that will hold information for timer, it will be passed trough A_EventInfo when Timer is launched
    tmr:=tmrs[Function]:={func:Function,Period:Period="on" ? 250 : Period,Priority:Priority
								,OneTime:Period<0,params:IsObject(ParmObject)?ParmObject:Object()
								,Tick:A_TickCount}
    tmr.CBA := RegisterCallback(A_ThisFunc,"F",4,&tmr)
    return !!(tmr.tmr  := DllCall("SetTimer", PTR,0, PTR,0, UInt
								, (Period && Period!="On") ? Abs(Period) : (Period := 250)
								, PTR,tmr.CBA,"PTR")) ;Create Timer and return true if a timer was created
				, tmr.Tick:=A_TickCount
 }
 tmr := Object(A_EventInfo) ;A_Event holds object which contains timer information
 if IsObject(tmr) {
	 DllCall("KillTimer", PTR,0, PTR,tmr.tmr) ;deactivate timer so it does not run again while we are processing the function
	 If (current && tmr.Priority<current.priority) ;Timer with higher priority is already current so return
		 Return (tmr.tmr:=DllCall("SetTimer", PTR,0, PTR,0, UInt, 100, PTR,tmr.CBA,"PTR")) ;call timer again asap
	 current:=tmr
	 ,tmr.tick:=ErrorLevel :=Priority ;update tick to launch function on time
	 ,tmr.func(tmr.params*) ;call function
    if (tmr.OneTime) ;One time timer, deactivate and delete it
       return DllCall("GlobalFree", PTR,tmr.CBA)
				 ,tmrs.Remove(tmr.func)
	 tmr.tmr:= DllCall("SetTimer", PTR,0, PTR,0, UInt ;reset timer
				,((A_TickCount-tmr.Tick) > tmr.Period) ? 0 : (tmr.Period-(A_TickCount-tmr.Tick)), PTR,tmr.CBA,"PTR")
	 current= ;reset timer
 }
}

3

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

А для чего необходимы таймеры? Почему просто не опрашивать все объекты по очереди?

4

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

Malcev пишет:

А вот от меток, наверное, можно избавиться, там по ссылке упоминается:

With SetTimerF() you could make this even cleaner because you would only need one timer.

В коде из той темы метки заменили функциями (func и func2) - это ничего же не меняет: какая разница, что плодить, если всё равно приходится плодить?
Что касается функции SetTimerF() - я её вообще не осилил, там и оформление дурацкое и кажется полно ошибок, т.к. у всех DllCall вызовов там используются необъявленные переменные UInt и PTR (такое чувство, что их забыли взять в кавычки).

YMP пишет:

А для чего необходимы таймеры? Почему просто не опрашивать все объекты по очереди?

Для асинхронности же. Как можно опрашивать все объекты по очереди? Только циклом, а цикл - это уже непрерываемый поток.

5

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

Почему непрерываемый? Как и любой другой, он прерываем между соседними командами.

6

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

используются необъявленные переменные UInt и PTR (такое чувство, что их забыли взять в кавычки).

Да тоже были глюки с этим. Заключаешь в кавычки, и работает. Но при следующем запуске винды, код может работать и без кавычек.

Что касается функции SetTimerF()

Тоже не понял, разве не проще использовать "Bind" к объекту функции.

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

7

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

YMP пишет:

Почему непрерываемый? Как и любой другой, он прерываем между соседними командами.

Разве прерываем? Я так и до конца и не освоил threads и всё, что с ними связано. Насколько знаю я - если сделать
Loop и внутри поочерёдную проверку объектов, то этот loop будет непрерываемым (только если не добавить во внутрь какое-то доп. условие для прерывания, вроде If GetKeyState(), но тогда обратно мы завершить работу в loop уже не сможем вернуться.
Можете код показать с прерываемым циклом, позволяющим делать конкурентные асинхронные запросы к серверу?

8

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

Поток может быть прерван другим потоком, например, по нажатию хоткея.


Loop
{
    SoundBeep, 500, 300
    SoundBeep, 800, 500
    SoundBeep, 2000, 100
}

F12:: ExitApp
F11:: MsgBox, Перерыв

Что такое конкурентные запросы, к сожалению, не знаю.

9 (изменено: Malcev, 2016-01-04 19:55:50)

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

У меня только так получается через Msxml2.XMLHTTP:
Но у него есть следующие моменты:
https://autohotkey.com/boards/viewtopic … 984#p52984

link1 := "http://forum.script-coding.com/viewforum.php?id=13&p=1"
link2 := "http://220454.lv"
loop 2
{
   req%A_Index% := ComObjCreate("Msxml2.XMLHTTP")
   req%A_Index%.Open("GET",link%A_Index%,true)
   req%A_Index%.onreadystatechange := Func("Ready").Bind(A_Index)
   req%A_Index%.send()
}
return
#Persistent

Ready(n) {
    if (req%n%.readyState != 4)  ; Not done yet.
       return
    MsgBox % req%n%.responseText
    return
}

Как это сделать с помощью WinHttpRequest не пойму.
Потому что такой код неправильный, так как скрипт зацикливается на загрузке второго несуществующего сайта.

link1 := "http://forum.script-coding.com/viewforum.php?id=13&p=1"
link2 := "http://220454.lv"
ComObjError(false)
#persistent
HttpObj1 := ComObjCreate("WinHttp.WinHttpRequest.5.1")
HttpObj2 := ComObjCreate("WinHttp.WinHttpRequest.5.1")

HttpObj1.Open("GET",link1,true) ;the third parameter means, that it will be working asynchronously
HttpObj1.Send()
SetTimer, HttpObj1AsynchHandler, -1
HttpObj2.Open("GET",link2,true)
HttpObj2.Send()
SetTimer, HttpObj2AsynchHandler, -1
Return

OnHttpResponse(HttpObj) {
    MsgBox % HttpObj.ResponseText
}

HttpObj1AsynchHandler:
    HttpObj1.WaitForResponse()
    OnHttpResponse(HttpObj1)
Return
HttpObj2AsynchHandler:
    HttpObj2.WaitForResponse()
    OnHttpResponse(HttpObj2)
Return

10

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

Для WaitForResponse можно таймаут указать, судя по его описанию, в секундах.

11

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

http://forum.script-coding.com/viewtopic.php?id=6877
Осталось на автохотки перевести.

12

Re: AHK: Асинхронная отправка нескольких запросов и получение ответов

Вот, что ответил Lexikos:

It is possible, but not with WaitForResponse(). If you wait for the response, you are essentially making the request "synchronous".
WinHttpRequest does not allow you to assign a function reference as an event handler.
ComObjConnect does not support non-dispatch event interfaces like the one WinHttpRequest exposes.
infogulch wrote a wrapper which supports async events and another wrapper to facilitate multiple requests.

https://github.com/infogulch/WinHttpReq … equest.ahk
https://github.com/infogulch/AsyncHttp/ … ncHttp.ahk