2017-10-06 36 views
0

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

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

因此,当出现一个新文件时,A和B都会找到该文件,并且都会尝试读取它。这会导致两种情况之间的某种竞争条件。我希望如果A在B之前开始读取文件,B将简单地跳过该文件并让A处理并删除它。如果B首先找到该文件,则同样的事情,A将不做任何事情。

现在我该如何实现这一点,在文件上设置锁定是不够的我猜是因为让我们说A开始读取文件,然后A被锁定,然后A将解锁它以删除它。在那段时间B可能会尝试读取文件。在这种情况下,文件被处理两次,这是不可接受的。因此,总而言之,无论何时文件出现在文件夹中,我都有一个程序和一个文件夹/网络共享的两个实例。我希望实例A或实例B处理该文件。从来没有,我怎么能在C#中实现这样的功能的任何想法?

回答

0

正确的方法是使用写入锁(例如System.IO.FileAccess.Write和读取共享(例如System.IO.FileShare.Read))打开文件。如果其中一个当其他进程已经打开文件时,进程会尝试打开该文件,然后open命令会抛出一个异常,您需要捕捉并处理您认为合适的(例如,日志和重试)。通过使用写入锁文件打开,你保证开启和锁定是原子,因此两个进程之间的同步,而且没有竞争条件

因此,像这样:

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。阅读是因为它允许我监视文件中发生了什么(例如,在记事本中打开它)。

为配合删除该文件是一个类似的原则:第一重命名/移动文件和赶上,如果其他进程已重新命名/移动它发生IOException异常,然后打开改名/移动文件。您重命名/移动该文件以指示文件已被处理,并且应该被其他进程忽略。例如,用一个悬而未决的文件扩展名重新命名它,或者将它移动到一个待定目录。

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

+0

这很直截了当,但是因为我也想删除文件(在代码中使用using语句之后会出现File.Delete命令),您如何确保其他实例不会尝试访问文件之间的结尾使用语句(文件锁)和删除?你能将你的代码扩展到完整流程吗? – ithinkyes

0

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

分发

让你的2个进程,使他们只能在某些文件的工作负载。你如何做到这一点可以基于文件名或日期/时间。例如。进程1读取具有以奇数结尾的时间戳的文件,并且进程2读取具有偶数的那些文件。

数据库的锁

另一种方法是,你使用某种数据库的锁。
进程1读取文件并根据文件名执行插入数据库表(必须是唯一的)。如果插入工作,那么它负责文件并继续处理它,否则如果插入失败,则另一个进程已经插入它,因此它是负责任的,并且进程1忽略该文件。

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

+0

感谢您的回复,但实例并不相互了解。只有“连接”他们的事情是他们可以到达相同的文件夹。彼此之间不存在依赖关系。 – ithinkyes

0

取而代之的是文件访问改变的深度,我建议使用功能服务器的方法。此方法的其他参数是来自不同计算机的文件使用情况。在访问和权限管理方面,这个特别的事情很深入。

我的建议是即将有文件访问(文件库)的单点实现以下功能:

  1. 获取的文件列表。 (获取可用文件列表)
  2. 签出文件。 (专有的对文件的获取权限,以便结账的拥有者有权修改文件)
  3. 修改文件。 (更新文件的内容或删除)
  4. 入住更改库

有很多的方法来实现的方法。 (一个使用API​​的文件的文件版本系统,实现服务;使用数据库,...)

一个容易(需要支持事务处理的数据库,触发器或存储过程)

  1. 获取文件列表。 (来自“可用文件表”的SQL SELECT)
  2. 签出文件。 (SQL UPDATE或更新存储过程。通过触发器或存储过程中的更新,在多次结帐的情况下定义“引发错误”状态)
  3. 修改文件。 (更新文件内容或删除它请记住,这是为了更好地做功能“服务器”。在这种情况下,您将需要实施安全策略一次
  4. 检入到存储库的更改在特定文件条目中释放“签出”字段。在事务中执行签入)
0

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

而且你必须添加检查到你的文件处理逻辑,服务不会试图“锁定”已被“锁定”的文件(名称以点开头)。

UPD可能是包含特殊字符集(如标记)和某些服务标识符(机器名与PID连接) 更好,因为我不确定文件重命名将如何在并发模式下工作。 所以,如果你已经在共享文件夹

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

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

如果你没有,你可以使用另一个网络共享,并尝试创建其他文件锁的标记,例如用于file.txt服务可以尝试创建(并保持写锁)新文件中像file.txt.lock已经创建了第一个服务的写权限锁文件关注原始文件并仅在处理原始文件时移除锁。

相关问题