很多 AHK 新手脚本写到后面,都会遇到一个很隐蔽的问题:脚本本身没报错,但数据偶尔乱了,日志偶尔断了,导出的文件偶尔损坏。
我见过不少这种情况,最后原因其实很简单:两个脚本、两个热键、两个定时任务,在同一时间写了同一个文件,或者同时处理了同一个目录。
FileLockAHK 就是为这类场景整理的 AHK v1 锁工具库。它不追求概念复杂,而是把日常脚本里最常用的“只能有一个任务在跑”整理成几种直接能用的函数。
为什么 AHK 脚本也需要锁
很多人刚开始写 AHK,只会想到 #SingleInstance。它能防止脚本重复运行,但它管不到这些情况:两个不同脚本写同一个 CSV、文件监控回调连续触发、热键被连按、定时任务上一次还没跑完下一次又来了。
这时候就需要锁。拿到锁,说明现在可以处理;拿不到锁,就等一下或直接退出。
文件锁最小示例
#Requires AutoHotkey v1.1
#Include <FileLockAHK>
lock := FileLock_Acquire(A_ScriptDir . "\data.lock", 3000)
if (!IsObject(lock)) {
MsgBox, 48, FileLockAHK, 文件正在被其他脚本处理
ExitApp
}
; 这里写需要互斥执行的代码
FileAppend, 安全写入一行`n, %A_ScriptDir%\data.txt, UTF-8
lock.Release()
这个例子适合保护 CSV、日志、缓存、导出文件。只要其他脚本也按同一个锁路径来获取锁,就能避免同时写入。
目录锁和任务锁
如果你的脚本是处理一个投递目录,比如用户把文件丢进来,脚本负责扫描、改名、移动,那么目录锁会更顺手。
lock := FileLock_AcquireDirectory(A_ScriptDir . "\投递目录", 5000)
if (IsObject(lock)) {
; 独占处理这个目录
lock.Release()
}
如果是定时任务、热键任务、文件监控回调,我更常用任务锁。它按任务名生成锁,不需要自己想锁文件名。
lock := FileLock_AcquireTask("每天生成报价单", 0)
if (!IsObject(lock)) {
MsgBox, 48, FileLockAHK, 任务已经在运行
ExitApp
}
; 执行任务
lock.Release()
命名互斥锁适合单实例
命名互斥锁不依赖锁文件,适合做“这个后台任务只允许有一个实例”的场景。
mutex := FileLock_AcquireMutex("ahk66_BackgroundJob", 0)
if (!IsObject(mutex)) {
MsgBox, 48, FileLockAHK, 程序已经有实例在运行
ExitApp
}
; 程序主逻辑
mutex.Release()
我建议怎么用
新手先不用把锁想得太复杂。只记住三句话:写同一个文件,用文件锁;处理同一个目录,用目录锁;防止任务重复跑,用任务锁或命名互斥锁。
资源包里的 demo 会演示文件锁、目录锁、任务锁和命名互斥锁,并生成演示日志。建议先双击跑一遍,看清楚“第二把锁被阻止”的效果,再放进自己的项目。
此库的能力与边界
文件锁依赖 Windows 文件共享机制,适合 AHK 脚本之间协作。脚本被强制结束时,系统会释放句柄,但锁文件可能残留;所以实际项目里,建议锁文件带上 PID、时间等元信息,方便排查。
如果你的自动化已经开始处理真实文件、真实报表、真实订单,这类锁库非常值得提前加上。它不显眼,但能少掉很多偶发问题。
FileLockAHK.ahk【文章底部有完整示例】
完整demo代码片段展示:
#NoEnv
#SingleInstance Force
SetBatchLines -1
#Include <FileLockAHK>
输出目录 := A_ScriptDir . "\示例输出"
FileCreateDir, %输出目录%
日志文件 := 输出目录 . "\FileLockAHK_演示日志.txt"
FileDelete, %日志文件%
Demo_Log("FileLockAHK 文件锁 / 命名互斥锁 / 目录锁 / 任务锁演示")
Demo_Log("")
; 1. 文件锁:适合保护同一个文件,避免多个脚本同时写入。
文件锁路径 := 输出目录 . "\01_文件写入.lock"
文件锁 := FileLock_Acquire(文件锁路径, 1000)
if (IsObject(文件锁)) {
Demo_Log("一、文件锁:获取成功 -> " . 文件锁路径)
第二把文件锁 := FileLock_Acquire(文件锁路径, 0)
Demo_Log(" 同一时间再次获取:" . (IsObject(第二把文件锁) ? "异常:不应该成功" : "正常:第二把锁被阻止"))
FileAppend, 这段内容在文件锁保护下写入。`n, % 输出目录 . "\01_被保护写入.txt", UTF-8
文件锁.Release()
Demo_Log(" 文件锁已释放")
} else {
Demo_Log("一、文件锁:获取失败")
}
Demo_Log("")
; 2. 目录锁:适合投递目录、下载目录、报表目录,一次只让一个任务处理。
投递目录 := 输出目录 . "\02_投递目录"
目录锁 := FileLock_AcquireDirectory(投递目录, 1000)
if (IsObject(目录锁)) {
Demo_Log("二、目录锁:获取成功 -> " . 投递目录)
FileAppend, 模拟投递文件`n, %投递目录%\待处理.txt, UTF-8
目录锁.Release()
Demo_Log(" 目录锁已释放")
} else {
Demo_Log("二、目录锁:获取失败")
}
Demo_Log("")
; 3. 任务锁:适合防止定时任务、热键任务、文件监控回调重复运行。
任务锁 := FileLock_AcquireTask("生成日报任务", 1000, 100, {"Root": 输出目录 . "\03_任务锁"})
if (IsObject(任务锁)) {
Demo_Log("三、任务锁:获取成功 -> 生成日报任务")
任务锁.Release()
Demo_Log(" 任务锁已释放")
} else {
Demo_Log("三、任务锁:获取失败")
}
Demo_Log("")
; 4. 命名互斥锁:不产生锁文件,适合防止脚本重复运行。
互斥锁 := FileLock_AcquireMutex("FileLockAHK_Demo_Mutex", 1000)
if (IsObject(互斥锁)) {
Demo_Log("四、命名互斥锁:获取成功 -> FileLockAHK_Demo_Mutex")
第二把互斥锁 := FileLock_AcquireMutex("FileLockAHK_Demo_Mutex", 0)
Demo_Log(" 同名互斥锁再次获取:" . (IsObject(第二把互斥锁) ? "异常:不应该成功" : "正常:第二把锁被阻止"))
互斥锁.Release()
Demo_Log(" 命名互斥锁已释放")
} else {
Demo_Log("四、命名互斥锁:获取失败")
}
FileRead, 日志内容, %日志文件%
MsgBox, 64, FileLockAHK 示例完成, % "演示完成!`n`n输出目录:" . 输出目录 . "`n日志文件:" . 日志文件 . "`n`n演示日志:`n" . 日志内容
Run, %输出目录%
ExitApp, 0
Demo_Log(text) {
global 日志文件
line := text . "`r`n"
FileAppend, %line%, %日志文件%, UTF-8
}

评论(0)