【问题标题】:Why does File.Move allow 2 threads to move the same file at the same time?为什么 File.Move 允许 2 个线程同时移动同一个文件?
【发布时间】:2014-09-19 10:40:36
【问题描述】:

我们目前有一个应用程序可以监控文件夹中的新文件。为了使其具有容错性并能够一次处理更多文件,我们希望能够在不同的机器上运行该应用程序的多个实例。我们使用File.Move 来“锁定”一个文件,并确保一次只有一个线程可以处理一个文件。

为了测试只有一个应用程序和/或线程可以对文件执行File.Move,我创建了一个简单的应用程序(基于原始应用程序的代码),它为每个应用程序创建 10 个线程并监视一个文件夹,当每个线程检测到一个新文件,它对其执行File.Move 并更改文件的扩展名,以尝试阻止其他线程执行相同操作。

我在运行此应用程序的多个副本(并且它自己运行)时看到了一个问题,其中 2 个线程(在同一个应用程序中或不同的应用程序中)都成功执行 File.Move 没有抛出异常,但是最后执行它的线程(我将文件的扩展名更改为包含DateTime.Now.ToFileTime()),成功地重命名了文件。 我查看了File.Move 的作用,它在执行操作之前检查文件是否存在,然后调用Win32Native.MoveFile 执行移动。

正如我所料,所有其他线程/应用程序都会抛出异常。

这是一个问题的原因是:

  1. 我认为一次只有 1 个线程可以对文件执行 File.Move
  2. 我需要可靠地让一个应用程序/线程一次能够处理一个文件。

这是执行File.Move的代码:

public bool TryLock(string originalFile, out string changedFileName)
{
    FileInfo fileInfo = new FileInfo(originalFile);
    changedFileName = Path.ChangeExtension(originalFile, ".original." + DateTime.Now.ToFileTime());
    try
    {
        File.Move(originalFile, changedFileName);
    }
    catch (IOException ex)
    {
        Console.WriteLine("{3} - Thread {1}-{2} File {0} is already in use", fileInfo.Name, Thread.CurrentThread.ManagedThreadId, id, DateTime.Now.ToLongTimeString());
        return false;
    }
    catch (Exception ex)
    {
        Console.WriteLine("{3} - Thread {1}-{2} File {0} error {4}", fileInfo.Name, Thread.CurrentThread.ManagedThreadId, id, DateTime.Now.ToLongTimeString(), ex);
        return false;
    }
    return true;
}

注意 - id 只是我分配给每个线程以进行日志记录的序号。

我正在使用 NTFS 的 SSD 上运行 Windows 7 Enterprise SP1。

【问题讨论】:

  • 你为什么不用lock
  • move = 复制到新的目的地,然后删除原来的。
  • @YuvalItzchakov 这应该只允许一个线程打开文件,但正如下面的答案所述,我们可能会在释放锁和删除文件时进入竞争状态。
  • 你有分布式架构吗?
  • @K.B 是的,我们有 4 个内部生产服务器,但目前只有一个处于活动状态(由于可靠的文件“锁定”)。我们架构的其余部分由 NServiceBus、MSMQ、Azure Worker Roles 和 Azure Service Bus 组成。

标签: c# multithreading file-io


【解决方案1】:

MSDN description 我假设File.Move 不会以独占模式打开文件。

如果您尝试跨磁盘卷移动文件并且该文件正在使用中, 文件被复制到目的地,但没有从 来源。

无论如何,我认为您最好创建自己的移动机制并让它在复制文件(然后删除它)之前以独占模式打开文件:

File.Open(pathToYourFile, FileMode.Open, FileAccess.Read, FileShare.None);

如果移动操作已经在进行中,其他线程将无法打开它。在完成副本(因此您需要处理文件句柄)和删除它之间,您可能会遇到竞争条件问题。

【讨论】:

  • 谢谢@marceln。我们一直在尝试这种方法,并在比赛条件方面得出了相同的结论。这是我们首先要避免的。
  • 文件打开后无法移动。
  • @usr:我知道,这就是我说复制然后删除它的原因。
【解决方案2】:

使用File.Move 作为锁是行不通的。正如@marceln 的回答中所述,它不会删除已在其他地方使用并且没有“锁定” 行为的源文件,您无法对其进行中继。

我的建议是使用BlockingCollection<T> 来管理您的文件处理:

// Assuming this BlockingCollection is already filled with all string file paths
private BlockingCollection<string> _blockingFileCollection = new BlockingCollection<string>();

public bool TryProcessFile(string originalFile, out string changedFileName)
{
    FileInfo fileInfo = new FileInfo(originalFile);
    changedFileName = Path.ChangeExtension(originalFile, ".original." + DateTime.Now.ToFileTime());

    string itemToProcess;
    if (_blockingFileCollection.TryTake(out itemToProcess))
    {
        return false;
    }

    // The file should exclusively be moved by one thread,
    // all other should return false.

    File.Move(originalFile, changedFileName);
    return true;
}

【讨论】:

  • 原因很简单,BlockingCollection 使用 SlimSemaphore 来确保仅在单台机器上而不是跨网络同步
  • OP 的测试是指多个线程访问单个文件。这就是这个答案的意义所在。 OP 没有说明如何跨多个实例访问他的文件。
  • 查看他对我的问题的评论
  • 那应该在原始问题中描述。
  • 问题确实表明“我们希望能够在不同的机器上运行此应用程序的多个实例
【解决方案3】:

您是跨卷还是在卷内移动?在后一种情况下,不需要复制。

.

@usr 在生产中,一旦一个线程“锁定”了一个文件,我们将在网络共享中移动它

我不确定这是真正的移动还是复制操作。在任何情况下,您都可以:

  1. 以独占方式打开文件
  2. 复制数据
  3. 按句柄删除源 (Deleting or Renaming a file using an open handle)

这允许您在移动期间将其他进程锁定在该文件之外。这更像是一种解决方法,而不是真正的解决方案。希望对您有所帮助。

请注意,在移动期间,文件不可用,其他进程访问它时会收到错误消息。您可能需要在操作之间有时间延迟的重试循环。

这里有一个替代方案:

  1. 将文件复制到具有不同扩展名的目标文件夹中,而该扩展名被读者忽略
  2. 自动重命名文件以删除扩展名

在同一卷上重命名始终是原子的。读者可能会在很短的时间内收到共享冲突错误。同样,您需要一个重试循环或容忍一个非常小的不可用窗口。

【讨论】:

    【解决方案4】:

    基于@marceln 和@YuvalItzchakov answer/cmets,我尝试了以下方法,这似乎给出了更可靠的结果:

    using (var readFileStream = File.Open(originalFile, FileMode.Open, FileAccess.Read, FileShare.Delete))
    {
        readFileStream.Lock(0, readFileStream.Length - 1);
        File.Move(originalFile, changedFileName);
        readFileStream.Unlock(0, readFileStream.Length - 1);
    }
    

    我想使用 Windows 自己的文件复制,因为它应该比复制流更有效,并且在生产中,我们会将文件从一个网络共享移动到另一个网络共享。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-18
      • 1970-01-01
      • 2023-01-16
      相关资源
      最近更新 更多