Pat*_*ick 4 python multithreading readdirectorychangesw pywin32 python-3.x
我尝试监视目录树的内容,其中包含大量文件(例如,许多目录每个目录有 9000 个文件)。
同步模式:
我首先尝试在阻塞模式(同步)下使用ReadDirectoryChangesW,但是当我删除监视的目录时,我最终陷入死锁,我无法检测也无法退出。
#
# Monitors a directory for changes and pass the changes to the queue
#
def MonitorDirectory(self, out_queue):
print("Monitoring instance \'{0}\' is watching directory: {1}".format(self.name, self.path))
# File monitor
FILE_LIST_DIRECTORY = 0x0001
buffer = win32file.AllocateReadBuffer(1024 * 64)
hDir = win32file.CreateFile(self.path,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,
None)
# Monitor directory for changes
while not self._shutdown.is_set():
# Create handle to directory if missing
#if os.path.isdir(self.path):
self.fh.write("ReOpen Exists {0}\n".format(os.path.isdir(self.path)))
self.fh.flush()
try:
hDir = win32file.CreateFile(self.path,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,
None)
except:
self.fh.write("Handle is dead\n")
self.fh.flush()
try:
self.fh.write("{0}\n".format(newH))
self.fh.flush()
except:
self.fh.write("Write failed\n")
self.fh.flush()
self.fh.write("Check Changes\n")
self.fh.flush()
results = win32file.ReadDirectoryChangesW(hDir,
1024 * 64,
True,
win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
win32con.FILE_NOTIFY_CHANGE_SIZE |
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
win32con.FILE_NOTIFY_CHANGE_SECURITY,
None,
None)
# Add all changes to queue
for action, file in results:
self.fh.write("Action: {0} on {1}\n".format(action, file))
out_queue.put((action, time.time(), os.path.join(self.path, file)))
self.fh.flush()
#else:
# Done main loop
print("Monitoring instance \'{0}\' has finished watching directory: {1}".format(self.name, self.path))
Run Code Online (Sandbox Code Playgroud)
当监视的目录被删除时,似乎没有办法避免调用阻塞?
此外,由于该函数在线程中运行,因此当死锁时我无法从“主管”线程中杀死它,该线程将监视父目录以查找监视目录上的删除操作,我真的不喜欢这是一个好的解决方案,因为它涉及太多更多代码。
异步模式:
然后我尝试了重叠模式(异步),它不会陷入死锁,但我无法检测目录句柄何时因删除目录而变为无效。WaitForSingleObject调用只是超时,检查目录是否存在os.path.isdir没有帮助,因为如果同时重新创建目录,它不会返回 False,但旧目录句柄仍然无效并且将不检测新创建的同名目录中的更改。
经过几天的尝试各种方法后,我终于找到了这段代码,但是它不能完美地工作,因为它仍然没有检测到监视目录的删除,并且在快速批量删除文件时它也确实丢失了一些文件。这是同步模式所没有的。
#
# Monitors a directory for changes and pass the changes to the queue
#
def MonitorDirectory(self, out_queue):
print("Monitoring instance \'{0}\' is watching directory: {1}".format(self.name, self.path))
# File monitor
FILE_LIST_DIRECTORY = 0x0001
overlapped = pywintypes.OVERLAPPED()
overlapped.hEvent = win32event.CreateEvent(None, False, 0, None)
buffer = win32file.AllocateReadBuffer(1024 * 64)
# Main loop to keep watching active
while not self._shutdown.is_set():
# Open directory
try:
hDir = win32file.CreateFile(self.path,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS | win32con.FILE_FLAG_OVERLAPPED,
None)
except:
# Wait before retry
time.sleep(1)
else:
# Monitor directory for changes
while not self._shutdown.is_set():
win32file.ReadDirectoryChangesW(hDir,
buffer,
True,
win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
win32con.FILE_NOTIFY_CHANGE_SIZE |
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
win32con.FILE_NOTIFY_CHANGE_SECURITY,
overlapped,
None)
# Wait for the changes
rc = win32event.WaitForSingleObject(overlapped.hEvent, 10000)
if rc == win32event.WAIT_OBJECT_0:
try:
bytes_returned = win32file.GetOverlappedResult(hDir, overlapped, True)
except:
raise Exception("Error: handle invalid?")
else:
# Get the changes
for action, file in win32file.FILE_NOTIFY_INFORMATION(buffer, bytes_returned):
out_queue.put((action, time.time(), os.path.join(self.path, file)))
elif rc == win32event.WAIT_TIMEOUT:
print("Monitoring instance \'{0}\': Timeout, no actions")
else:
raise Exception("Error?! RC = {0}".format(rc))
# Done main loop
print("Monitoring instance \'{0}\' has finished watching directory: {1}".format(self.name, self.path))
Run Code Online (Sandbox Code Playgroud)
有没有办法处理删除监视目录的检测,而不是仅仅删除win32con.FILE_SHARE_DELETE标志?
现在,关于FILE_SHARE_DELETE的几句话(可以在[MS.Docs]: CreateFileW function上找到一些关于它的文档):
黄金法则(或不可变法则,如果你愿意的话)是用户不能真正删除具有打开句柄的文件(或dir ) 。
尝试删除或重命名(这似乎与当前问题无关,但事实并非如此)具有打开句柄的目录可能会产生不同的结果(取决于创建句柄的方式以及用于重命名/删除dir的API) :
我测试了上述场景,尝试以各种方式删除/重命名目录:
rmdir /q /s,move /y我开始研究解决问题的方法,并遇到了[MS.Docs]:GetFinalPathNameByHandleW 函数(win32file.GetFinalPathNameByHandle)。玩过它:
Run Code Online (Sandbox Code Playgroud)>>> import sys >>> import os >>> import win32api >>> import win32file >>> import win32con >>> >>> print("Python {:s} on {:s}\n".format(sys.version, sys.platform)) Python 3.5.4 (v3.5.4:3f56838, Aug 8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32 >>> os.listdir() ['code00.py', 'test'] >>> test_dir = ".\\test" >>> os.path.abspath(test_dir) 'e:\\Work\\Dev\\StackOverflow\\q049652110\\test' >>> h = win32file.CreateFile(test_dir, win32con.GENERIC_READ, win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE, None, win32con.OPEN_EXISTING, win32con.FILE_FLAG_BACKUP_SEMANTICS, None) >>> h <PyHANDLE:620> >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test' >>> test_dir1 = test_dir + "1" >>> os.rename(test_dir, test_dir1) >>> os.listdir() ['code00.py', 'test1'] >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test1' >>> os.rename(test_dir1, test_dir) >>> os.listdir() ['code00.py', 'test'] >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test' >>> os.unlink(test_dir) Traceback (most recent call last): File "<stdin>", line 1, in <module> PermissionError: [WinError 5] Access is denied: '.\\test' >>> # Delete the dir from elsewhere (don't use os.rmdir since that will only schedule the dir for deletion) ... >>> os.listdir() ['code00.py'] >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) '\\\\?\\E:\\$RECYCLE.BIN\\S-1-5-21-1906798797-2830956273-3148971768-1002\\$RY7SH8D' >>> os.mkdir(test_dir) >>> os.listdir() ['code00.py', 'test'] >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) '\\\\?\\E:\\$RECYCLE.BIN\\S-1-5-21-1906798797-2830956273-3148971768-1002\\$RY7SH8D' >>> os.rmdir(test_dir) # Since the new "test" dir wasn't open, operation successful >>> os.listdir() ['code00.py'] >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) '\\\\?\\E:\\$RECYCLE.BIN\\S-1-5-21-1906798797-2830956273-3148971768-1002\\$RY7SH8D' >>> # Restore the dir from RECYCLE.BIN ... >>> os.listdir() ['code00.py', 'test'] >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test' >>> os.rmdir(test_dir) # Still an open handle, scheduled to be deleted >>> os.listdir() ['code00.py', 'test'] >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test' >>> win32api.CloseHandle(h) >>> os.listdir() ['code00.py'] # After closing the handle the dir was deleted >>> h <PyHANDLE:0> >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED) Traceback (most recent call last): File "<stdin>", line 1, in <module> pywintypes.error: (6, 'GetFinalPathNameByHandle', 'The handle is invalid.')
注意:我还尝试了[MS.Docs]: GetFileInformationByHandle 函数(win32file.GetFileInformationByHandle),但我无法重现该行为,即使使用 3 个pywintypes.datetime字段之一(应该是上次访问/修改时间)也无法重现该行为; 重命名/删除dir时,没有任何信息发生变化。我没有花时间去调查,我想到了2个可能的原因:
该数据以某种方式存储在HANDLE “内部” ,并且该函数在调用时实际上并不查询FS (与GetFinalPathNameByHandle相反)
当目录被重命名/删除时,其父目录的这些日期字段会发生变化
所以,我们似乎有一个胜利者。我只想发布算法(代码应该相当简单):
其他可能的方法(尽管不可取):
检索描述指定目录中的更改的信息。该函数不会报告对指定目录本身的更改。
关于“事件丢失”,正如我在其他答案中指定的那样,无法确保所有事件都会得到处理,只有方法可以最大程度地减少丢失的数量。
| 归档时间: |
|
| 查看次数: |
3618 次 |
| 最近记录: |