65.9K
CodeProject 正在变化。 阅读更多。
Home

BlockingFileObserver - 一个 IEnumerable<string> 的实现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (6投票s)

2007 年 10 月 13 日

公共领域

5分钟阅读

viewsIcon

27541

downloadIcon

198

BlockingFileObserver - 一个 IEnumerable<string> 的实现

Screenshot - BlockingFileEnumerator.jpg

引言

你是否曾经有过需要监视目录以查找新文件的经历?对我来说,当然有过,无论是监视我的 IIS SMTP 服务的BadMail目录,还是监视来自其他进程的传入文件,等等。

现在,通常我会设置我的 FileSystemWatcher,注册事件等等,然后愉快地阻塞并等待传入的文件。然而,在某个时候,我发现这种方法很乏味,坦白说……很无聊。我想要做的是使用一个更友好的接口来实现这种“功能”。

我理想中想要的是一个 IEnumerable<span class="code-keyword"><string> 实例,我可以对其进行枚举,对其执行 LINQ 查询,并总体上拥有一个更通用的接口来处理。

我想要的是这样

foreach(string fileName in BlockingFileObserver.Create
    (@"D:\projects", "*.txt", FileOperation.None | FileOperation.Create))
    Console.WriteLine("File Notification: {0}", fileName);

在上面的代码中,我做了一个非常有趣的假设。“我的线程应该阻塞,直到生成器中的下一个项出来。”嗯……做梦去吧。

这就是本文随附的小玩具项目所要做的。它包含一个名为 BlockingFileObserver 的类,该类是 IEnumerable<span class="code-keyword"><string> 的子类,它将处理目录中文件更改的监视,并在文件到达时通知您,在下一个文件到来之前一直阻塞。

项目中提供的类比上面的代码更进一步。BlockingFileObserver 很好用,直到你需要关闭它。上面的语法将一直运行下去。停止包含的控制台应用程序的唯一方法是按 Ctrl-C。因此,我添加了另一个功能,其中包含一个 out Timer stopTimer 和一个 long millis 参数,以指示我们希望在经过特定时间间隔后停止枚举。这种语法如下所示

foreach(string fileName in BlockingFileObserver.Create
    (@"D:\projects", "*.txt", FileOperation.None | FileOperation.Create, 
    out timer, 30000))
    Console.WriteLine("File Notification: {0}", fileName);

我花了一些时间为自己证明为什么我需要 out Timer stopTimer 参数。我得出的结论是,这是将 Timer 的事件暴露给调用者的唯一方法,以防它也需要超时通知。每当我把一些 while(true) { // 阻塞执行 ... } 代码放到外面时,我都想在它停止运行时得到通知。

BlockingFileObserver 上总共有四个工厂方法,暴露了大多数可能的参数组合

public static BlockingFileObserver Create(string folderPath);
public static BlockingFileObserver Create
    (string folderPath, string fileTypeFilter);
public static BlockingFileObserver Create
    (String folderPath, FileOperation opType);
public static BlockingFileObserver Create
    (String folderPath, string fileTypeFilter, FileOperation opType);
public static BlockingFileObserver Create
    (String folderPath, string fileTypeFilter, FileOperation opType, 
    out Timer stopTimer, long millis);   

FileOperation 是项目中包含的一个 enum 类型,定义如下

/// <summary>

/// Defines the three basic file type operations, and 

/// one more, None, meaning an unspecified value

/// </summary>

[Flags]
public enum FileOperation 
{ 
    Create = 1,
    Delete = 2,
    Change = 4,
    None = 8
}

由于它被 [Flags] 装饰,所以它的值可以一起使用 '|' 运算符,例如 FileOperation.None | FileOperation.Delete 等等。这给接口带来了一点额外的灵活性,允许您使用非常熟悉的语法——如果不是非常漂亮的话——来指定您感兴趣的通知类型。

背景

最重要的是,我们将创建此类枚举的能力归功于 yield 关键字。简单来说,当在方法或属性 getter 中使用时,yield 允许执行流程返回到调用者,当调用者请求生成器的下一个元素时,执行将从最后一个 yield return 语句之后继续。也许这比描述更容易说明。这就是 yieldBlockingFileObserver 中的用法

public IEnumerator<string> GetEnumerator() 
{
    // ok, do the existing files first, 

    // but only if we've .None in our FileOperation ... 

    if ((FileOperation.None & this.FileOperation) > 0) 
    {
        foreach (string fileName in Directory.GetFiles
            (this.FolderPath, this.FilterString))
        yield return fileName;
    }          
            ...
}
</string>

每次我们命中 yield return 语句时,执行就会继续到调用者的端。紧接着,执行将从这里恢复。这让我可以设置一个 FileSystemWatcher,注册我需要的事件,并阻塞我的线程的执行,直到我收到事件通知。

为了阻塞直到收到事件,我不得不深入研究 System.Threading 命名空间。在那里,我找到了 ManualResetEvent 类。

ManualResetEvent 有两种状态:已触发或未触发。它有用之处在于,如果您调用 .WaitOne(),您之后的下一行代码将在有人将 ManualResetEvent 的状态设置为已触发时执行。您将阻塞,直到收到继续的信号。我使用它来阻塞,直到我从内部 FileSystemWatcher 收到文件通知事件。这看起来像这样(这些是前一段代码片段之后的几行,仍在 BlockingFileObserverGetEnumerator() 方法中)

// ok, now I need to create a FileSystemWatcher here ... 

m_Watcher = new FileSystemWatcher(this.FolderPath, this.FilterString);
            
// Init the watcher, register for events etc. 

... 
            
// Do I need to keep listening ?

if (0 == keepListening)
    yield break;

// start the watcher ... 

m_Watcher.EnableRaisingEvents = true;

// Now I need to start waiting for events ... 

while (true)
{
    m_ResetEvent.WaitOne();
    m_ResetEvent.Reset();
    
    if (this.Active)
        yield return m_LastFile;
    else 
    {
        yield break;
        break;
    }
}

这里的“魔法”在 while(true) 循环中。我们立即进入循环,阻塞,直到 ManualResetEvent 被触发。我们重置它,以便它返回到未触发状态,然后继续返回下一个可用值,或者使用 yield break 退出循环。

事件在 FileSystemWatcher 的事件处理程序代码中被触发

#region FileSystemWatcher & eventing fields & methods
/// <summary>

/// Will observe our folder for new/deleted/updated files and notify us ... 

/// </summary>

private FileSystemWatcher m_Watcher = null;

/// <summary>

/// Will help us block execution until the FileSystemWatcher 

/// posts a new event ... 

/// </summary>

private ManualResetEvent m_ResetEvent = new ManualResetEvent(false);

/// <summary>

///  Little "buffer" variable, to hold the last returned file name

/// </summary>

private string m_LastFile = string.Empty;

private void Watcher_PostEvent(object sender, FileSystemEventArgs e) 
{ 
    // ok, store the file name ... 

    m_LastFile = e.Name;
    // post an event ... 

    m_ResetEvent.Set();
}

#endregion

ManualResetEvent 成员在 Active 属性的 set 部分也被触发。这是必需的,与 GetEnumerator() 方法中我 while 循环的“特定”语法相结合,以确保我不会遇到 Active 属性设置为 false,但我仍在等待最后一个 FileSystemWatcher 事件然后退出。

private bool m_Active = true;

public bool Active
{
    get 
    { 
        return m_Active; 
    }
    set 
    { 
        m_Active = value;
        // In case I'm blocking right now ... notify

        if (!m_Active)
            m_ResetEvent.Set();
    }
}

Using the Code

该类真的很小,我认为将其放在单独的 ClassLibrary 项目中没有用,所以它包含在项目中的唯一一个 .cs 文件,即 Program.cs 中。我在其中仔细定义了区域,因此通过简单的复制粘贴将其提取出来应该不会太麻烦。

除此之外,使用该类非常直接。使用四个可用的 .Create(...) static 方法之一,来获取一个实例,该实例的行为将与其他任何 IEnumerable<span class="code-keyword"><string> 类似。

class Program
{
    static void Main(string[] args)
    {
        Timer timer;            
        // Enumerate ... 

        foreach(string fileName in BlockingFileObserver.Create
            (@"D:\projects", "*.txt", FileOperation.None | 
            FileOperation.Create, out timer, 30000))
        Console.WriteLine("File Notification: {0}", fileName);

        Console.WriteLine("Finished enumerating");
        Console.ReadLine();
    }
}

关注点

最后一个有趣的细节是也注册了枚举超时功能的 .Create 实现。在那里,我实例化了 out Timer stopTimer 参数,并为其提供了一个匿名委托来处理其 CallBack。有趣的是,由于在创建时我对想要停用的 BlockingFileObserver 实例有引用,我可以在我的匿名委托的正文中! 我认为这非常漂亮……

public static BlockingFileObserver Create
    (String folderPath, string fileTypeFilter, FileOperation opType, 
    out Timer stopTimer, long millis)
{
    BlockingFileObserver enumerator = 
        Create(folderPath, fileTypeFilter, opType);
    // Create the new timer ... 

    stopTimer = new Timer
                (new TimerCallback(
                delegate(object sender)
                {
                    enumerator.Active = false;
                }),
                null,
                millis,
                Timeout.Infinite);
    return enumerator;
}

我认为这是一个强大的面向服务型“监听器”的编程范例。将所有阻塞的复杂性隐藏在 IEnumerable<span class="code-keyword"><T> 接口后面非常通用,但最重要的是,在我看来,它非常漂亮。

© . All rights reserved.