独占锁定文件然后删除/移动它

GDe*_*ini 14 .net c# winapi file-locking

我正在C#中实现一个应该监视目录的类,在删除文件时处理这些文件,然后在处理完成后立即删除(或移动)处理过的文件.由于可以有多个线程运行此代码,第一个运行此代码的线程将其锁定,因此没有其他线程将读取相同的文件,并且没有外部进程或用户可以以任何方式访问.我想保持锁定直到文件被删除/移动,因此没有其他线程/进程/用户访问它的风险.

到目前为止,我尝试了2个实现选项,但它们都没有按我的意愿工作.

选项1

FileStream fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Delete);
//Read and process
File.Delete(file.FullName); //Or File.Move, based on a flag
fs.Close();
Run Code Online (Sandbox Code Playgroud)

选项2

FileStream fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
//Read and process
fs.Close();
File.Delete(file.FullName); //Or File.Move, based on a flag
Run Code Online (Sandbox Code Playgroud)

选项1的问题是其他进程可以访问该文件(它们可以删除,移动,重命名),同时应该完全锁定.

选项2的问题是文件在被删除之前被解锁,因此其他进程/线程可以在删除发生之前锁定文件,因此删除将失败.

我正在寻找一些可以使用我已经拥有独占访问权限的文件句柄执行删除的API.

编辑

被监视的目录驻留在pub共享中,因此其他用户和进程可以访问它. 问题不是在我自己的进程中管理锁.我试图解决的问题是如何独占锁定文件然后移动/删除它而不释放锁

Jim*_*hel 6

我想到了两种解决方案.

第一个也是最简单的是让线程将文件重命名为其他线程不会触及的内容.像" filename.dat.<unique number>"这样<unique number>的东西,特定于线程的东西.然后线程可以在文件上派对它想要的一切.

如果两个线程同时获取该文件,则只有其中一个线程能够重命名该文件.您将不得不处理在其他线程中发生的IOException,但这不应该是一个问题.

另一种方法是让一个线程监视目录并将文件名放入BlockingCollection.工作线程从该队列中获取项目并处理它们.因为只有一个线程可以从队列中获取该特定项,所以没有争用.

BlockingCollection解决方案是一点点(但只有一点点)更为复杂的设置,但应该执行比具有多线程监控的相同目录下更好的解决方案.

编辑

您编辑的问题会更改问题.如果您在一个可公开访问的目录中有一个文件,那么它在被放置的时间和线程锁定它之间的任何时刻都有被查看,修改或删除的风险.

由于您在打开文件时无法移动或删除文件(我不知道),最好的办法是让文件将文件移动到不可公开访问的目录中.理想情况下,对于已锁定的目录,只有运行应用程序的用户才能访问.所以你的代码变成:

File.Move(sourceFilename, destFilename);
// the file is now in a presumably safe place.
// Assuming that all of your threads obey the rules,
// you have exclusive access by agreement.
Run Code Online (Sandbox Code Playgroud)

编辑#2

另一种可能性是独占打开文件并使用您自己的复制循环复制它,在复制完成后保持文件打开.然后,您可以回放文件并进行处理.就像是:

var srcFile = File.Open(/* be sure to specify exclusive access */);
var destFile = File.OpenWrite(/* destination path */);
// copy the file
var buffer = new byte[32768];
int bytesRead = 0;
while ((bytesRead = srcFile.Read(buffer, 0, buffer.Length)) != 0)
{
    destFile.Write(buffer, 0, bytesRead);
}
// close destination
destFile.Close();
// rewind source
srcFile.Seek(0, SeekOrigin.Start);
// now read from source to do your processing.
// for example, to get a StreamReader, just pass the srcFile stream to the constructor.
Run Code Online (Sandbox Code Playgroud)

您有时可以处理然后复制.这取决于当您完成处理时流是否保持打开状态.通常,代码执行以下操作:

using (var strm = new StreamReader(srcStream, ...))
{
    // do stuff here
}
Run Code Online (Sandbox Code Playgroud)

最终关闭流和srcStream.你必须像这样写你的代码:

using (var srcStream = new FileStream( /* exclusive access */))
{
    var reader = new StreamReader(srcStream, ...);
    // process the stream, leaving the reader open
    // rewind srcStream
    // copy srcStream to destination
    // close reader
}
Run Code Online (Sandbox Code Playgroud)

可行,但笨拙.

哦,如果你想在删除它之前消除某人读取文件的可能性,只需在关闭文件之前将文件截断为0.如:

srcStream.Seek(0, SeekOrigin.Begin);
srcStream.SetLength(0);
Run Code Online (Sandbox Code Playgroud)

这样一来,如果有人在你开始删除它之前就已经达到它,那就没有什么可以修改的了,等等.


ese*_*elk 6

这是我所知道的最强大的方法,如果您在多个服务器上有多个处理这些文件的进程,它甚至可以正常工作。

不是锁定文件本身,而是创建一个临时文件进行锁定,这样您就可以毫无问题地解锁/移动/删除原始文件,但仍要确保至少在任何服务器/线程/进程上运行的代码的任何副本都将不要尝试同时处理文件。

伪代码:

try
{
    // get an exclusive cross-server/process/thread lock by opening/creating a temp file with no sharing allowed
    var lockFilePath = $"{file}.lck";
    var lockFile = File.Open(lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);

    try
    {
        // open file itself with no sharing allowed, in case some process that does not use our locking schema is trying to use it
        var fileHandle = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None);

        // TODO: add processing -- we have exclusive access to the file, and also the locking file

        fileHandle.Close();

        // at this point it is possible for some other process that does not use our locking schema to lock the file before we
        //  move it, causing us to process this file again -- we would always have to handle issues where we failed to move
        //  the file anyway (maybe we just lost power, or crashed?) so we had to design around this no matter what

        File.Move(file, archiveDestination);
    }
    finally
    {
        lockFile.Close();

        try
        {
            File.Delete(lockFilePath);
        }
        catch (Exception ex)
        {
            // another process opened locked file after we closed it, before it was deleted -- safely ignore, other process will delete lock file
        }
    }
}
catch (Exception ex)
{
    // another process already has exclusive access to the lock file, we don't need to do anything
    // or we failed while processing, in which case we did not move the file so it will be tried again by this process or another
}
Run Code Online (Sandbox Code Playgroud)

这种模式的一个好处是它也可以用于文件存储支持锁定的时候。例如,如果您尝试处理 FTP/SFTP 服务器上的文件,您可以使临时锁定文件使用普通驱动器(或 SMB 共享)——因为锁定文件不必与文件本身。

我不能相信这个想法,它比 PC 存在的时间更长,并且被许多应用程序使用,例如 Microsoft Word、Excel、Access 和大多数较旧的数据库系统。阅读:经过良好测试。


小智 3

您可能需要从生成线程实现某种形式的共享/同步列表。如果父线程通过定期检查目录来跟踪文件,那么它可以将它们交给子线程,这将消除锁定问题。