为什么.NET的File.Open使用UNC路径进行过多的SMB调用?

时间:2014-11-25 21:42:03

标签: c# c++ .net file-io windows-networking

我有一段代码需要使用UNC路径从NAS服务器打开和读取大量小文本文件。此代码是最初用C ++编写但现在转换为C#的模块的一部分。 C#版本明显变慢了。我确定打开文件的调用几乎占据了所有的性能差异。使用WireShark我发现这是因为System.IO.File.Open调用产生的SMB网络请求远远多于类似的C ++代码。

C ++代码进行了这个调用:

FILE *f = _wfsopen(fileName, L"r", _SH_DENYWR);

这导致以下SMB请求序列:

NT Create AndX Request, FID: 0x0004, Path: \\a\\i\\a\\q\\~141106162638847.nmd
NT Create AndX Response, FID: 0x0004
Trans2 Request, QUERY_FILE_INFO, FID: 0x0004, Query File Basic Info
Trans2 Response, FID: 0x0004, QUERY_FILE_INFO
Read AndX Request, FID: 0x0004, 1327 bytes at offset 0
Read AndX Response, FID: 0x0004, 1327 bytes
Close Request, FID: 0x0004
Close Response, FID: 0x0004
NT Create AndX Request, FID: 0x0005, Path: \\a\\i\\a\\q\\~141106162638847.nmd
NT Create AndX Response, FID: 0x0005

C#代码进行此调用:

FileStream f = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);

这导致以下SMB请求序列:

Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a\\i\\a\\q\\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \\a\\i\\a\\q\\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a\\i\\a\\q\\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: 
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: 
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \\a
Trans2 Response, FIND_FIRST2, Files: a
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \\a\\i
Trans2 Response, FIND_FIRST2, Files: i
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a\\i
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \\a\\i
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \\a\\i\\a
Trans2 Response, FIND_FIRST2, Files: a
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a\\i\\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \\a\\i\\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \\a\\i\\a\\q
Trans2 Response, FIND_FIRST2, Files: q
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a\\i\\a\\q\\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \\a\\i\\a\\q\\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a\\i\\a\\q\\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: 
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: 
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \\a
Trans2 Response, FIND_FIRST2, Files: a
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \\a\\i
Trans2 Response, FIND_FIRST2, Files: i
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a\\i
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \\a\\i
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \\a\\i\\a
Trans2 Response, FIND_FIRST2, Files: a
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \\a\\i\\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \\a\\i\\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \\a\\i\\a\\q
Trans2 Response, FIND_FIRST2, Files: q
Close Request, FID: 0x000f
Close Response
NT Create AndX Request, FID: 0x0018, Path: \\a\\i\\a\\q\\~141106162638847.nmd
NT Create AndX Response, FID: 0x0018
Trans2 Request, QUERY_FILE_INFO, FID: 0x0018, Query File Basic Info
Trans2 Response, FID: 0x0018, QUERY_FILE_INFO
Read AndX Request, FID: 0x0018, 1327 bytes at offset 0
Read AndX Response, FID: 0x0018, 1327 bytes
Close Request, FID: 0x0018
Close Response, FID: 0x0018
NT Create AndX Request, FID: 0x0019, Path: \\a\\i\\a\\q\\~141106162638847.nmd
NT Create AndX Response, FID: 0x0019

为什么System.IO.File.Open会发出所有这些额外的SMB请求?有没有办法更改此代码以避免所有这些额外请求?

2 个答案:

答案 0 :(得分:12)

简而言之,File.Open调用new FileStream()new FileStream()会执行很多的调用:

  1. NormalisePath

    String filePath = Path.NormalizePath(path, true, maxPath); // fullCheck: true
    
  2. 导致this代码:

    1.a:获取完整路径:

        if (fullCheck) { ... 
            result = newBuffer.GetFullPathName();
    

    GetFullPathName()调用Win32Native.GetFullPathName一两次(取决于生成路径的长度)。

    1.B。试图扩大短途径。您的路径包含~个字符,因此它看起来像是path expanding的候选者:

        if (mightBeShortFileName) {
            bool r = newBuffer.TryExpandShortFileName();
    

    因此,Win32Native.GetLongPathName()is called

    1. FileIoPermission.Demand()(仅限非信任):

      // All demands in full trust domains are no-ops, so skip 
      if (!CodeAccessSecurityEngine.QuickCheckForAllDemands()) {
          ...
          new FileIOPermission(secAccess, control, new String[] { filePath }, false, false).Demand();
      
    2. Open fileStream(软盘反击;)):

       // Don't pop up a dialog for reading from an emtpy floppy drive
      int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS);
      try {
          ...
          _handle = Win32Native.SafeCreateFile(tempPath, fAccess, share, secAttrs, mode, flagsAndAttributes, IntPtr.Zero);
      
    3. Win32Native.GetFileType()

    4. 并非所有这些都会导致smb请求,但有些人会这样做。我试图通过调试源步骤(here's手册来启用.net源调试)并在每个步骤后检查日志来重现繁琐的请求。 Resuts与您的第一个列表更相似。 如果您真的对找到真正的问题感兴趣,那么您必须自己做。

      UPD 请注意,我已检查过当前(.net 4.5.2)的行为。它自2.0以来被多次更改(例如FileIOPermission.Demand()原来也被称为完全受信任的代码),所以它取决于:)

答案 1 :(得分:7)

我真的没有具体回答为什么.NET实现如此繁琐,但是 这种行为是由于System.IO.FileStream的实施,因为File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);所做的就是将参数传递给FileStream constructor

public static FileStream Open(string path, FileMode mode, FileAccess access, FileShare share)
{
    return new FileStream(path, mode, access, share);
}

更改FileStream的行为意味着您基本上必须重新实现FileStream类,这需要付出很多努力。

另一个更简单的替代方法是创建一个调用您提供的C ++代码的本机包装器。然后拨打native wrapper from your C# code