C#

时间:2017-10-06 07:57:51

标签: c# file concurrency filesystems locking

我有一个应用程序将从文件夹中读取并等待文件出现在此文件夹中。当此文件出现时,应用程序应读取内容,使用文件中的数据向外部系统执行一些功能,然后删除文件(然后等待下一个文件)。

现在,我想在两台不同的机器上运行此应用程序,但两者都在同一个文件夹中监听。所以它是完全相同的应用程序,但有两个实例。我们称之为实例A和实例B.

因此,当出现新文件时,A和B都会找到该文件,并且两者都会尝试读取它。这将导致两个实例之间出现某种竞争条件。我希望如果A开始在B之前读取文件,B只需跳过该文件并让A进程并删除它。同样的事情,如果B先找到文件,A就什么都不做。

现在我怎么能实现这一点,设置锁定文件是不够的我猜是因为让我们说A开始读取文件,然后由A锁定,然后A将解锁它以便删除它。在此期间,B可能会尝试读取该文件。在这种情况下,文件被处理两次,这是不可接受的。

总而言之,每当文件出现在文件夹中时,我就会有一个程序和一个文件夹/网络共享的两个实例。我希望EITHER实例A或实例B处理该文件。从来没有,关于如何在C#中实现这样的功能的任何想法?

4 个答案:

答案 0 :(得分:0)

我可以想到两个快速解决方案;

分发负载

拥有2个进程,以便它们只能处理某些文件。如何执行此操作可能基于文件名或日期/时间。例如。进程1读取时间戳以奇数结尾的文件,进程2读取具有偶数的文件。

数据库锁定

另一种选择是你使用某种数据库作为锁 进程1读取文件并根据文件名插入数据库表(必须是唯一的)。如果插入有效,那么它负责该文件并继续处理它,否则如果插入失败,则另一个进程已经插入它以便它负责并且进程1忽略该文件。

两个进程都必须可以访问数据库,这会产生一些开销。但如果您想将其扩展到更多流程,则可能是更好的选择。

答案 1 :(得分:0)

执行此操作的正确方法是使用写锁定打开文件(例如,System.IO.FileAccess.Write和读取共享(例如,System.IO.FileShare.Read)。如果其中一个进程尝试在另一个进程打开文件时打开该文件,然后open命令将抛出一个异常,您需要根据需要捕获并处理该异常(例如,log和retry)。通过对文件使用写锁定打开,你保证开启和锁定是原子的,因此在两个进程之间同步,并且没有竞争条件。

这样的事情:

try
{
    using (FileStream fileStream = new FileStream(FileName, FileMode.Open, FileAccess.Write, FileShare.Read))
    {
        // Read from or write to file.
    }
}
catch (IOException ex)
{
    // The file is locked by the other process. 
    // Some options here:
    // Log exception.
    // Ignore exception and carry on.
    // Implement a retry mechanism to try opening the file again.
}

如果您希望其他进程在程序打开时根本无法访问该文件,则可以使用FileShare.None。我更喜欢FileShare.Read,因为它允许我监视文件中发生的事情(例如,在记事本中打开它)。

为了满足删除文件的原则是一个类似的原则:首先重命名/移动文件并捕获在其他进程已经重命名/移动它时发生的IOException,然后打开重命名/移动的文件。您重命名/移动文件以指示文件已在处理中,并且应由其他进程忽略。例如,使用.pending文件扩展名重命名,或将其移动到Pending目录。

try
{
    // This will throw an exception if the other process has already moved the file - 
    // either FileName no longer exists, or it is locked.
    File.Move(FileName, PendingFileName);
    // If we get this far we know we have exclusive access to the pending file.
    using (FileStream fileStream = new FileStream(PendingFileName, FileMode.Open, FileAccess.Write, FileShare.Read))
    {
        // Read from or write to file.
    }
    File.Delete(PendingFileName);
}
catch (IOException ex)
{
    // The file is locked by the other process. 
    // Some options here:
    // Log exception.
    // Ignore exception and carry on.
    // Implement a retry mechanism to try moving the file again.
}

与打开文件一样,File.Move是原子的并受锁保护,因此可以保证,如果有多个并发线程/进程尝试移动文件,则只有一个会成功,其他的会抛出异常。请点击此处查看类似问题:Atomicity of File.Move

答案 2 :(得分:0)

相反,我会建议使用功能 - 服务器方法来深入了解文件访问权限。此方法的其他参数是来自不同计算机的文件使用。在访问和权限管理方面,这一点非常重要。

我的建议是要有一个单点文件访问(文件存储库),它实现了以下功能:

  1. 获取文件列表。 (获取可用文件列表)
  2. 结帐文件。 (对文件的专有抓取访问权限,以便结帐的所有者有权修改文件)
  3. 修改文件。 (更新文件内容或删除它)
  4. 签入对存储库的更改
  5. 有很多方法可以实现这种方法。 (使用文件的API和文件版本控制系统;实现服务;使用数据库,......)

    一个简单的(需要一个支持事务,触发器或存储过程的数据库)

    1. 获取文件列表。 (SQL#来自"可用文件表")
    2. 结帐文件。 (SQL UPDATE或更新存储过程。通过触发器或存储过程中的更新定义"在多次检出的情况下引发错误"状态)
    3. 修改文件。 (更新文件内容或删除它。请记住,直到更好地完成某项功能&#34;服务器&#34;。在这种情况下,您需要实施一次安全策略)< / LI>
    4. 签入对存储库的更改(发布&#34;签出&#34;提交特定文件条目。在交易中实施签到)

答案 3 :(得分:0)

因此,如果您要应用锁定,可以尝试使用文件名作为锁定对象。您可以尝试以特殊方式重命名文件(例如通过在文件名前添加点) 并且第一个幸运重命名文件的服务将继续使用它。第二个(慢)将获得文件不存在的异常。

你必须在你的文件处理逻辑中加上检查,服务不会试图“锁定”已经“锁定”的文件(名字以点开头)。

UPD可能最好包含一组特殊字符(如标记)和一些服务标识符(与PID连接的机器名) 因为我不确定文件重命名在并发模式下是如何工作的。 因此,如果共享文件夹中有file.txt

  • 首先你必须检查文件名中是否有.lock字符串 已经
  • 如果没有服务可以尝试将其重命名为file.txt.lockDevhost345(其中.lock - 特殊标记,Devhost - 当前计算机的名称和345是一个PID(进程)标识符)
  • 然后服务必须检查是否有file.txt.lockDevhost345个文件 可用

如果是 - 它被当前服务实例锁定并可以使用 如果不是 - 它被并发服务“窃取”,因此不应该被处理。

如果您没有写入权限,则可以使用其他网络共享并尝试创建其他文件锁定标记,例如file.txt服务可以尝试创建(并保持写锁定)新文件,如{{1已创建锁定文件的第一个服务正在处理原始文件,并仅在处理原始文件时删除锁定。