如何在AutoIt中非阻塞调用Log_Info函数避免主脚本阻塞
实现AutoIt日志库的非阻塞调用
AutoIt本身不支持原生异步函数调用,但可以通过以下两种可靠方式实现日志操作的非阻塞执行,避免API请求阻塞主脚本:
方案一:独立进程异步日志
将日志写入+时间戳请求逻辑拆分为独立脚本,主脚本通过启动独立进程执行日志操作,无需等待其完成。
步骤1:编写独立日志脚本(AsyncLogger.au3)
#include <Constants.au3> #include <FileConstants.au3> #include <File.au3> #include <WinHttp.au3> ; 接收命令行参数:日志文件路径、日志级别、日志内容 If $CmdLine[0] < 3 Then Exit 1 Local $sLogFile = $CmdLine[1] Local $sLogLevel = $CmdLine[2] Local $sContent = $CmdLine[3] ; 原有时间戳获取函数 Func _Get_Timestamp($timezone = "Europe/Berlin", $timeout = 5000) Local $sServerName = "timeapi.io" Local $sObjectName = "/api/Time/current/zone?timeZone=" & $timezone Local $hSession = _WinHttpOpen() If @error Then Return "TIMESTAMP NOT AVAILABLE" Local $hConnect = _WinHttpConnect($hSession, $sServerName) If @error Then _WinHttpCloseHandle($hSession) Return "TIMESTAMP NOT AVAILABLE" EndIf Local $hRequest = _WinHttpOpenRequest($hConnect, "GET", $sObjectName) If @error Then _WinHttpCloseHandle($hConnect) _WinHttpCloseHandle($hSession) Return "TIMESTAMP NOT AVAILABLE" EndIf _WinHttpSetTimeouts($hRequest, $timeout, $timeout, $timeout, $timeout) If Not _WinHttpSendRequest($hRequest) Then _WinHttpCloseHandle($hRequest) _WinHttpCloseHandle($hConnect) _WinHttpCloseHandle($hSession) Return "TIMESTAMP NOT AVAILABLE" EndIf If Not _WinHttpReceiveResponse($hRequest) Then _WinHttpCloseHandle($hRequest) _WinHttpCloseHandle($hConnect) _WinHttpCloseHandle($hSession) Return "TIMESTAMP NOT AVAILABLE" EndIf Local $sResponse = "", $sBuffer While 1 $sBuffer = _WinHttpReadData($hRequest) If @error Or $sBuffer = "" Then ExitLoop $sResponse &= $sBuffer WEnd _WinHttpCloseHandle($hRequest) _WinHttpCloseHandle($hConnect) _WinHttpCloseHandle($hSession) Local $aMatch = StringRegExp($sResponse, '"dateTime"\s*:\s*"([^"]+)"', 1) If @error Or UBound($aMatch) = 0 Then Return "TIMESTAMP NOT AVAILABLE" Local $sDateTime = StringReplace(Stringleft($aMatch[0],19), "T", " ") Return $sDateTime = "" ? "TIMESTAMP NOT AVAILABLE" : $sDateTime EndFunc ; 日志写入逻辑 Func _Write_Log($log_file, $content, $level) Local $sTimestamp = _Get_Timestamp() Local $sLogLine = "[" & $sTimestamp & "] [" & $level & "] " & $content & @CRLF ; 确保日志目录存在 Local $sDir = StringLeft($log_file, StringInStr($log_file, "\", 0, -1)) If Not FileExists($sDir) Then DirCreate($sDir) ; 写入日志 Local $hFile = FileOpen($log_file, $FO_APPEND) If $hFile = -1 Then Return False FileWrite($hFile, $sLogLine) FileClose($hFile) Return True EndFunc ; 执行日志写入并退出 _Write_Log($sLogFile, $sContent, $sLogLevel) Exit 0
步骤2:主脚本修改Log_Info为启动独立进程
Func Log_Info($log_file, $content) ; 转义特殊字符避免命令行解析错误 Local $sEscapedLogFile = StringReplace($log_file, '"', '""') Local $sEscapedContent = StringReplace($content, '"', '""') ; 启动独立日志进程,不等待完成 Run('"' & @AutoItExe & '" /AutoIt3ExecuteScript "AsyncLogger.au3" "' & $sEscapedLogFile & '" "INFO" "' & $sEscapedContent & '"') Return True EndFunc ; 原有调用逻辑保持不变 Func _RunCatch($program, $report = true) Local $pid = Run($program) Log_Info(Get_Logfile(), "Test wird ausgeführt: " & $program) if $report And @error > 0 Then MsgBox($MB_OK + $MB_ICONWARNING, "_RunCatch", "Der Test konnte nicht gestartet werden." & @CRLF & "Fehlermeldung: " & _WinAPI_GetErrorMessage(@error)) EndIf EndFunc ;==> _RunCatch()
方案二:队列+定时任务异步处理
通过全局队列缓存日志请求,用AdlibRegister注册定时任务,后台批量处理日志写入。
主脚本修改实现
#include <Constants.au3> #include <FileConstants.au3> #include <File.au3> #include <WinHttp.au3> ; 全局日志队列 Global $g_aLogQueue = [] ; 注册定时处理任务,每500ms检查一次队列 AdlibRegister("_Process_Log_Queue", 500) ; 非阻塞日志入队函数 Func Log_Info($log_file, $content) _ArrayAdd($g_aLogQueue, [$log_file, $content, "INFO"]) Return True EndFunc ; 定时处理队列的后台函数 Func _Process_Log_Queue() If UBound($g_aLogQueue) = 0 Then Return ; 取出队列首个日志条目 Local $aLogEntry = $g_aLogQueue[0] _ArrayDelete($g_aLogQueue, 0) ; 执行原有日志写入逻辑 _Write_Log($aLogEntry[0], $aLogEntry[1], $aLogEntry[2]) EndFunc ; 保留原有_Get_Timestamp和_Write_Log函数 Func _Get_Timestamp($timezone = "Europe/Berlin", $timeout = 5000) ; 原函数代码不变 EndFunc Func _Write_Log($log_file, $content, $level) ; 原函数代码不变 EndFunc ; 原有调用逻辑保持不变 Func _RunCatch($program, $report = true) Local $pid = Run($program) Log_Info(Get_Logfile(), "Test wird ausgeführt: " & $program) if $report And @error > 0 Then MsgBox($MB_OK + $MB_ICONWARNING, "_RunCatch", "Der Test konnte nicht gestartet werden." & @CRLF & "Fehlermeldung: " & _WinAPI_GetErrorMessage(@error)) EndIf EndFunc ;==> _RunCatch()
额外优化建议
- 缩短API超时时间:将
_Get_Timestamp的超时从5秒改为2秒,减少资源占用 - 缓存时间戳:短时间内复用上次获取的时间戳(比如1分钟内),减少API请求次数
- 本地时钟降级:API请求失败时,优先使用本地系统时间,避免日志缺失
内容的提问来源于stack exchange,提问作者Doppelbemme
相关产品推荐
相关产品推荐

