美文网首页C++
Windows下使用FindFirstChangeNotific

Windows下使用FindFirstChangeNotific

作者: 天律界中子 | 来源:发表于2016-01-19 20:41 被阅读3945次

问题背景

在一个项目中,有ConfigReceiverConfigApply两个进程需要通过配置文件进行协作。ConfigReceiver进程负责接收远程发送过来的配置信息,并写入配置文件config.ini,而ConfigApply进程中有一个定时器定时(1s)读取config.ini,如果发现配置更改,就应用新的配置。(请不要吐槽这种机制,历史原因,咳咳。。)
config.ini所在的文件夹中,只有config.ini一个文件会发生改变。

分析

在定时器的回调里,每次都读取config.ini,代价太高,所以考虑监控config.ini文件的变化情况,只有当其内容发生变化时,才去实际地读取文件。
Windows下,可以监控文件(夹)改变的API有两个:FindFirstChangeNotificationReadDirectoryChangesW,前者能监控文件夹发生变化,但无法知道具体是哪个文件发生了变化;后者则可以具体到文件。
上述问题中,由于文件夹中可变的文件只有一个,所以很自然地选择使用前者。

解决方案

ConfigApply进程的主线程中设置定时器及一个bool值bFileChange,初始化为true。然后开启一个线程中,在线程中使用FindFirstChangeNotification监视config.ini所在文件夹的状态变化。如果状态发生变化,就将bFileChange设为true
在定时器的回调里,只有bFileChangetrue时,才读取config.ini并判断配置是否发生改变,并设置bFileChangefalse

代码如下:
ConfigReceiver进程,main.cpp:

/*
    ConfigReceiver进程,使用随机Sleep()函数模拟接收远程配置。
*/
#include <Windows.h>
#include <tchar.h>
#include <time.h>
#include <iostream>

LPCTSTR lpszDir = _T("D:\\");
LPCTSTR lpszFile = _T("D:\\config.ini");
LPCTSTR lpszSection = _T("Switch");
LPCTSTR lpszKey = _T("Open");

int main()
{
    srand(_time32(NULL));
    SYSTEMTIME st = {0};

    while (true)
    {
        // 随机睡眠
        DWORD ms = rand() % 10; // 0-9
        Sleep(ms * 1000);

        // 更改配置
        ::WritePrivateProfileString(lpszSection, lpszKey, 0 == ms % 2 ? _T("0") : _T("1"), lpszFile);

        ::GetLocalTime(&st);
        std::cout << st.wMinute << ":" << st.wSecond << "\t更改配置:" <<  ms % 2 << std::endl;
    }
    return 0;
}

ConfigApply进程,main.cpp:

/*
    ConfigApply进程。
*/
#include <Windows.h>
#include <tchar.h>
#include <process.h>
#include <iostream>

LPCTSTR lpszDir = _T("D:\\");
LPCTSTR lpszFile = _T("D:\\config.ini");
LPCTSTR lpszSection = _T("Switch");
LPCTSTR lpszKey = _T("Open");

bool    g_bFileChange = true;
int     g_nOpen = 0;
bool    g_bWatch = true;

// 定时器回调函数
VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);

// 文件夹监控线程函数
unsigned int __stdcall ThreadProc(void* param);

// 应用配置
void Apply();

int main()
{
    // 开启定时器和线程
    UINT_PTR uTimerID = ::SetTimer(NULL, 0, 1000, &TimerProc);
    HANDLE hThread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, &ThreadProc, NULL, 0, NULL));
    
    MSG msg;
    while (::GetMessage(&msg, NULL, 0, 0))
    {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }

    ::KillTimer(NULL, uTimerID);
    uTimerID = 0;

    g_bWatch = false;
    DWORD dwWait = ::WaitForSingleObject(hThread, 2000);
    if (WAIT_OBJECT_0 != dwWait)
    {
        ::TerminateThread(hThread, -1);
    }
    ::CloseHandle(hThread);
    hThread = NULL;

    return 0;
}

VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
    if (g_bFileChange)
    {
        g_bFileChange = false;

        int nOpen = ::GetPrivateProfileInt(lpszSection, lpszKey, g_nOpen, lpszFile);
        if (nOpen != g_nOpen)
        {
            g_nOpen = nOpen;
            Apply();
        }
        else
        {
            SYSTEMTIME st = {0};
            ::GetLocalTime(&st);

            std::cout << st.wMinute << ":" << st.wSecond << "\t文件变化,但配置不变" << std::endl;
        }
    }
}

unsigned int __stdcall ThreadProc(void* param)
{
    // 只监视实际的写入
    HANDLE hFind = ::FindFirstChangeNotification(lpszDir, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);
    if (INVALID_HANDLE_VALUE == hFind)
    {
        return -1;
    }

    while (g_bWatch)
    {
        DWORD dwWait = ::WaitForSingleObject(hFind, 1000);
        if (WAIT_OBJECT_0 == dwWait)
        {
            g_bFileChange = true;       // 文件改变
            if (!::FindNextChangeNotification(hFind))
            {
                ::FindCloseChangeNotification(hFind);
                hFind = NULL;
                return -2;
            }
        }
    }

    ::FindCloseChangeNotification(hFind);
    hFind = NULL;
    return 0;
}

void Apply()
{
    SYSTEMTIME st = {0};
    ::GetLocalTime(&st);

    std::cout << st.wMinute << ":" << st.wSecond << "\t配置改变,应用成功" << std::endl;
}

运行结果如下:


image.png

优化

可以看到,在ConfigApply进程退出时,要首先等待监视线程的退出。虽然最坏的情况下也只需等待1s,但在实际的应用中,还是很明显的。
为了改善这一情况,我刚开始考虑减少监视线程每次wait的时间。但是要将延迟降低到不可察觉的程度,如200ms,又会增加CPU的占用。
基于项目的实际情况,我在ConfigApply进程退出时,自行修改了一下配置文件以触发wait:
::WritePrivateProfileString(lpszSection, lpszKey, 0 == g_nOpen ? _T("0") : _T("1"), lpszFile); DWORD dwWait = ::WaitForSingleObject(hThread, 2000);,
则监视线程就可以立刻退出了。而且监视线程中的等待时间也可以设置为无限长:
DWORD dwWait = ::WaitForSingleObject(hFind, INFINITE);

相关文章

网友评论

    本文标题:Windows下使用FindFirstChangeNotific

    本文链接:https://www.haomeiwen.com/subject/jazkkttx.html