很多 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
}

 

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。