1

Тема: AHK: Запросы к серверу в несколько потоков

Здравствуйте.
Работаю с системой маркировки товара "Честный знак".
Возникла такая задача: перед загрузкой документа с кодами маркировки в систему нужно проверить статус ("в обороте", "выбыл") и текущего владельца кода маркировки.
Написал скрипт, который обращается к API системы и получает нужные данные.
Столкнулся с тем, что на больших списках кодов проверка длится очень долго (в среднем 2-3 секунды на один запрос).

Фрагмент кода:


; В начале скрипта происходит авторизация в системе, получаем от сервера Auth.token, который затем передается в заголовке каждого запроса

Loop, read, %inputFile%, %outFile%
{
if a_Index <5 ; Заголовок таблицы
{
	FileAppend, %A_LoopReadLine%`n
	continue
}

; Парамерты 
cis := SubStr(A_LoopReadLine, 1, 31)
cis := URIEncode(cis) ; Экранируем символы для передачи в GET запросе
url := "https://markirovka.crpt.ru/api/v3/true-api/products/listV2?cis=" . cis
; /Параметры

; Запрос
HTTP.Open("GET", url, true)
HTTP.SetRequestHeader("Authorization", "Bearer " . Auth.token)
HTTP.Send()
HTTP.WaitForResponse()
ResponseText := HTTP.ResponseText
; /Запрос

; Обработка ответа
FoundPos := RegExMatch(ResponseText, "O)\[(\{.*\})\]", Match)
ResponseText := Match.1
JSONobj := JSON.Parse(ResponseText)
txtLine := JSONobj.cis . "`tagent: " . JSONobj.agentInn . "`tstatus: " . JSONobj.status . "`n"
FileAppend %txtLine%
; /Обработка ответа

echo(A_Index) ;Выводим в консоль номер текущей строки файла, чтобы видеть прогресс выполнения
}
MsgBox Проверка завершена
ExitApp

Вопрос: Можно ли как-то выполнять несколько запросов параллельно, не дожидаясь ответа на предыдущие запросы, и обрабатывать ответы по мере поступления?
Была задумка вынести блоки "Запрос" и "Обработка ответа" в отдельный дочерний скрипт и запускать несколько его экземпляров с параметрами url и Auth.token. Но тут тоже могут быть проблемы. Во-первых, если один экземпляр откроет outFile для записи, остальные должны дождаться его закрытия, а я не знаю, как это реализовать. Во-вторых, основной скрипт должен как-то отслеживать количество запущенных экземпляров дочернего, поскольку API имеет ограничения по количеству запросов в секунду.
Буду рад любым идеям, желательно с примерами реализации.

2

Re: AHK: Запросы к серверу в несколько потоков

Вопрос: Можно ли как-то выполнять несколько запросов параллельно, не дожидаясь ответа на предыдущие запросы, и обрабатывать ответы по мере поступления?

API имеет ограничения по количеству запросов в секунду

Если API имеет ограничения по количеству запросов, почему Вы решили, что можно обрабатывать ответы по мере поступления? Может запрос был проигнорирован из-за ограничения по количеству запросов в секунду, и ждать ответа на него уже не имеет смысла. А может не был проигнорирован, просто проверка списка кодов длилась долго. До каких пор ждать?

Вообще можно сделать, чтобы каждый дочерний скрипт записывал в свой файл, а главный скрипт эти файлы периодически читал. Ещё существует Std out, обмен оконными сообщениями, отправка сообщений в порт. Можно сделать, чтобы каждый дочерний скрипт имел GUI-окно и выводил в него текст, а главный скрипт периодически считывал этот текст и удалял то, что уже прочитал.

3

Re: AHK: Запросы к серверу в несколько потоков

ypppu пишет:

Если API имеет ограничения по количеству запросов, почему Вы решили, что можно обрабатывать ответы по мере поступления? Может запрос был проигнорирован из-за ограничения по количеству запросов в секунду, и ждать ответа на него уже не имеет смысла. А может не был проигнорирован, просто проверка списка кодов длилась долго. До каких пор ждать?

API имеет ограничение в 10 запросов в секунду от одного клиента.
Один запрос выполняется в среднем 2-3 секунды. Иногда быстрее, бывает что дольше. Случается, что и по 10-15 секунд длится ожидание ответа.
Мой скрипт, как он есть сейчас, работает последовательно, то есть выполняет запросы по мере поступления ответов.
Вот так:
Запрос №1 --> ожидание --> ответ №1 --> обработка №1 --> запрос №2 --> ожидание --> ответ №2 ... и так далее
Я же хочу что-то вроде:

+ открыть спойлер

0000 мс: запрос №1
0200 мс: запрос №2
0400 мс: запрос №3
0600 мс: запрос №4
0521 мс: опа! пришел ответ №1 --> обработка №1
0800 мс: запрос №5
1000 мс: запрос №6
1111 мс: ага, пришел ответ №2
1112 мс: тем временем пришел ответ №3 --> обработка №2 --> обработка №3
1200 мс: запрос №7
1321 мс: ответ №5, ответ 4 где-то задерживается, ждем
1400 мс: запрос №8
1445 мс: наконец-то ответ №4 --> обработка №4 --> обработка №5
1600мс: запрос №9
...
и так далее

Как отправлять запросы, я кажется догадываюсь...


loop, read, %inputFile%
{
url := MakeURL(A_LoopReadLine) ; подготавливаем URL для запроса
Run, %A_AhkPath% HttpRequest.ahk url Auth.token
sleep 200
}

А вот как получать ответы, пока не понимаю.

ypppu пишет:

существует Std out, обмен оконными сообщениями, отправка сообщений в порт.

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

4

Re: AHK: Запросы к серверу в несколько потоков

Да, нужен "слушатель", вполне возможно, что это не самый подходящий вариант; у меня нет опыта по Std out. Я перечислил известные варианты вывода информации, смотрите что Вам ближе и понятнее.

5

Re: AHK: Запросы к серверу в несколько потоков

Асинхронно в одном скрипте через winhttprequest объект не получится, читайте про Msxml2.XMLHTTP.
А также:
http://forum.script-coding.com/viewtopic.php?id=10765

6 (изменено: KepocuH, 2020-07-02 12:52:01)

Re: AHK: Запросы к серверу в несколько потоков

Проблема подкралась откуда не ждал.
Оказывается, если создавать новый COM-объект winhttprequest, то для него токен, полученный другим winhttprequest'ом, является недействительным. Авторизация слетает, и API не выдает результат.
Отсюда вывод: запускать кучу дочерних скриптов не имеет смысла.

Вопрос многопоточности остается актуальным.

Ушел гуглить Msxml2.XMLHTTP.

UPD: Почитал. Оно умеет в асинхронные запросы, но обрабатывать ответы в ahk все равно приходится синхронно. Так что в моем случае отличий от winhttprequest нет.
Видимо, реализовать задачу в рамках ahk невозможно.

7 (изменено: Malcev, 2020-07-02 14:34:36)

Re: AHK: Запросы к серверу в несколько потоков

link1 := "http://forum.script-coding.com"
link2 := "https://www.google.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()
}
msgbox done
return


Ready(n)
{
   if (req%n%.readyState != 4)
      return
   msgbox % req%n%.responseText
   return
}

8

Re: AHK: Запросы к серверу в несколько потоков

Заработало!
Проверка правда все равно длится долго, но это уже проблемы на стороне сервера. 

+ Лог

14:33:09 Request # 1
14:33:09 Request # 2
14:33:10 Request # 3
14:33:10 Request # 4
14:33:10 Request # 5
14:33:10 Request # 6
14:33:10 Request # 7
14:33:11 Request # 8
14:33:11 Request # 9
14:33:11 Request # 10
14:33:11 Request # 11
14:33:11 Request # 12
14:33:12 Request # 13
14:33:12 Request # 14
14:33:12 Request # 15
14:33:12 Request # 16
14:33:12 Request # 17
14:33:13 Request # 18
14:33:13 Response # 1
14:33:13 Request # 19
14:33:13 Response # 2
14:33:13 Request # 20
14:33:13 Request # 21
14:33:13 Request # 22
14:33:14 Request # 23
14:33:14 Request # 24
14:33:14 Request # 25
14:33:14 Request # 26
14:33:14 Request # 27
14:33:15 Request # 28
14:33:15 Request # 29
14:33:15 Request # 30
14:33:15 Request # 31
14:33:15 Request # 32
14:33:15 Response # 3
14:33:16 Request # 33
14:33:16 Response # 4
14:33:16 Request # 34
14:33:16 Request # 35
14:33:16 Request # 36
14:33:17 Request # 37
14:33:17 Request # 38
14:33:17 Request # 39
14:33:17 Request # 40
14:33:17 Request # 41
14:33:18 Request # 42
14:33:18 Request # 43
14:33:18 Request # 44
14:33:18 Request # 45
14:33:18 Response # 5
14:33:18 Request # 46
14:33:19 Request # 47
14:33:19 Request # 48
14:33:19 Request # 49
14:33:19 Request # 50
14:33:19 Response # 6
14:33:19 Request # 51
14:33:21 Response # 7
14:33:24 Response # 9
14:33:53 Response # 8
14:33:56 Response # 11
14:33:59 Response # 12
14:34:00 Response # 10
14:34:02 Response # 13
14:34:03 Response # 14
14:34:05 Response # 15
14:34:07 Response # 16
14:34:08 Response # 17
14:34:11 Response # 18
14:34:12 Response # 19
14:34:13 Response # 21
14:34:14 Response # 20
14:34:18 Response # 22
14:34:21 Response # 23
14:34:22 Response # 25
14:34:23 Response # 26
14:34:25 Response # 27
14:34:29 Response # 24
14:34:32 Response # 29
14:34:39 Response # 30
14:34:42 Response # 31
14:34:45 Response # 32
14:34:48 Response # 33
14:34:51 Response # 34
14:34:57 Response # 35
14:35:00 Response # 36
14:35:01 Response # 28
14:35:02 Response # 37
14:35:02 Response # 38
14:35:03 Response # 40
14:35:05 Response # 39
14:35:07 Response # 41
14:35:09 Response # 42
14:35:28 Response # 43
14:35:31 Response # 45
14:35:33 Response # 46
14:35:36 Response # 47
14:35:39 Response # 48
14:35:42 Response # 49
14:35:45 Response # 50
14:35:48 Response # 44
14:36:31 Response # 51
>Exit code: 0    Time: 220.1

Спасибо огромное за помощь!