【问题标题】:C# File Move (Rename) Multi Thread Windows OSC# 文件移动(重命名)多线程 Windows 操作系统
【发布时间】:2013-11-15 18:10:52
【问题描述】:

我有 10,000 个 XML 文件要读取并保存到数据库的场景。我拥有的是 5 个 Windows 服务,它们都在尝试处理该文件夹。

我的技术是首先尝试重命名 (File.Move) 文件,并使用特定于给定服务实例的扩展名。

这是 99% 的时间。但是我看到的是文件系统将在 0.01% 的情况下允许两个请求同时尝试和重命名。

如何防止这种情况发生?这有意义吗?请参阅以下代码 sn-p 了解一下。我最终得到了大约 10 个 IO 异常文件。

 string[] sourceFiles = Directory.GetFiles(InputPath, string.Format(LocaleHelper.Culture, "*.{0}", Extention))
                                            .OrderBy(d => new FileInfo(d).CreationTime).ToArray();


            foreach (string file in sourceFiles)
            {
                var newFileName = string.Format(LocaleHelper.Culture, "{0}.{1}", file, CacheFlushManager.GetInstanceName);


                try
                {
                    // first we'll rename // however at this point the file may not even exist
                    // it will throw an exception and move onto the next file if it exists


                    File.Move(file, newFileName);



                    var xml = File.ReadAllText(newFileName);

                    // write to DB at this point we know its unique
                }
                catch (FileNotFoundException ex)
                {
                    Logger.LogDebug(string.Format(LocaleHelper.Culture, "{0} Couldn't read file : {1}", CacheFlushManager.GetInstanceName, newFileName));
                }
                catch (IOException ex)
                {
                    Logger.LogDebug(string.Format(LocaleHelper.Culture, "{0} Couldn't process file : {1}", CacheFlushManager.GetInstanceName, newFileName));
                }
                catch (Exception ex)
                {
                    Logger.LogError("Execute: Error", ex);

                    try
                    {
                        File.Move(newFileName, string.Format(LocaleHelper.Culture, "{0}.badfile", newFileName));
                    }
                    catch (Exception ex_deep)
                    {
                        Logger.LogError(string.Format("{0} Execute: Error Deep could not move bad file {1}", CacheFlushManager.GetInstanceName, newFileName), ex_deep);
                    }
                }

编辑 1

以下是我所看到的确切错误示例。我对文件如何根据我正在使用的代码使用确切的时间感到非常困惑?我是否完全摆脱了这个困境?

[7220] TransactionFileServiceProcess [11:28:32]:Service4 无法处理文件:C:\temp\Input\yap804.xml.Service4 System.IO.IOException:进程无法访问文件'C:\ temp\Input\yap804.xml.Service4' 因为它正被另一个进程使用。

编辑 2

这里是从“调试”的角度来看正在发生的事情。 Service 的 2 和 3 怎么会到达“END RENAME”?我认为这是问题的症结所在......想法?

问题在于文件yap620.xml.Service3 最终会因为文件操作错误而被搁置。

[6708] TransactionFileServiceProcess [10:54:38]: Service3 Start Rename: C:\temp\Input\yap620.xml.Service3 TransactionFileServiceProcess.Execute => BHSLogger.LogDebug =>     LoggerImpl.Write E[]

[4956] TransactionFileServiceProcess [10:54:38]: Service2 Start Rename: C:\temp\Input\yap620.xml.Service2 TransactionFileServiceProcess.Execute => BHSLogger.LogDebug => LoggerImpl.Write E[]

[7416] TransactionFileServiceProcess [10:54:38]: Service4 Start Rename: C:\temp\Input\yap620.xml.Service4 TransactionFileServiceProcess.Execute => BHSLogger.LogDebug => LoggerImpl.Write E[]

[6708] TransactionFileServiceProcess [10:54:38]: Service3 End Rename: C:\temp\Input\yap620.xml.Service3 TransactionFileServiceProcess.Execute => BHSLogger.LogDebug => LoggerImpl.Write E[]

[6708] TransactionFileServiceProcess [10:54:38]: Service3 Start Read: C:\temp\Input\yap620.xml.Service3 TransactionFileServiceProcess.Execute => BHSLogger.LogDebug => LoggerImpl.Write E[]

[4956] TransactionFileServiceProcess [10:54:38]: Service2 End Rename: C:\temp\Input\yap620.xml.Service2 TransactionFileServiceProcess.Execute => BHSLogger.LogDebug => LoggerImpl.Write E[]

[4956] TransactionFileServiceProcess [10:54:38]: Service2 Start Read: C:\temp\Input\yap620.xml.Service2 TransactionFileServiceProcess.Execute => BHSLogger.LogDebug => LoggerImpl.Write E[]

[6708] TransactionFileServiceProcess [10:54:38]: Service3 Couldn't process file : C:\temp \Input\yap620.xml.Service3 TransactionFileServiceProcess.Execute => BHSLogger.LogDebug => LoggerImpl.Write E[]

【问题讨论】:

  • 10,000 个 XML 文件?为什么不使用 JSON?
  • 不幸的是,我无法选择它们的文件格式。在这种情况下,这无关紧要。
  • 试图消除冲突是没有意义的——你似乎已经有足够的代码来避免它——所以只要期待发生冲突(或任何其他 IO 错误)并重试。
  • 您的意思是您有五个服务在同一个文件夹上同时执行此操作?
  • 你是说File.Move 方法对于尝试重命名同一个文件的两个不同进程成功了吗?这似乎不太可能。

标签: c# multithreading windows-services


【解决方案1】:

我看不出问题出在哪里。您有多个线程获取文件列表,然后尝试处理这些文件。有时线程试图重命名的文件不存在,有时文件存在但正在被另一个线程重命名。这两者中的任何一个都不应该是问题。在任何一种情况下,得到错误的线程都应该假设其他线程正在处理文件,然后继续。

当然,假设您没有其他进程正在访问该目录中的文件。

为什么您需要五个单独的服务实例来执行此操作,这超出了我的理解。您可以通过让一个进程执行Parallel.ForEach 来大大简化事情并减少不必要的 I/O。例如:

string[] sourceFiles = Directory.GetFiles(
    InputPath,
    string.Format(LocaleHelper.Culture, "*.{0}", Extention))
    .OrderBy(d => new FileInfo(d).CreationTime).ToArray();

Parallel.Foreach(sourceFiles, (file) =>
{
    // do file processing here
});

TPL 将分配多个线程来进行处理,并将工作项分配给线程。所以文件不可能被多个线程打开。

【讨论】:

  • 嗨,吉姆,感谢您的回答。回答您有关负载平衡方案的问题。假设他们在一个域上有 5 台不同的服务器。每台服务器都有一个运行的服务都指向一个文件夹共享。
【解决方案2】:

您是否有多个线程在同一个服务中运行?还是多个独立的服务?

如果您在同一个服务中有多个线程,只需创建一个Queue<FileInfo> 或类似的东西,并在线程空闲时从队列中删除项目。我相信标准 Queue 是线程安全的,因此您永远不应该两次处理同一个文件。

如果您有多个独立的服务,您可以考虑使用 LockFileFile.Open 并指定 FileShare.None

编辑:

我误解了你想要做什么。我以为您希望每个服务都处理所有文件。您确实需要在同一个服务中运行这些多个线程,或者允许某种通信方法允许不同的服务确定哪些文件已被处理。

【讨论】:

  • 嘿伙计.. 所以多个独立的服务每个都有 1 个线程。我将如何使用 LockFile 重命名?
  • 你不会重命名,你只会获得一个锁,任何试图同时访问文件的服务要么等待获得锁,要么记下该文件并重试稍后(或不)。
  • 但我想要的是如果有 10 个文件和 5 个服务进程,每个进程基本上处理 2 个。
  • 那你为什么不能通过一项服务完成这一切呢?
猜你喜欢
  • 1970-01-01
  • 2014-06-09
  • 2019-12-21
  • 2013-08-27
  • 2010-10-02
  • 1970-01-01
  • 2017-01-23
  • 2015-05-25
  • 1970-01-01
相关资源
最近更新 更多