Подтвердить что ты не робот

Почему .NET File.Open с UNC-каналом делает чрезмерные вызовы SMB?

У меня есть блок кода, который нужно открыть и прочитать много небольших текстовых файлов с сервера NAS с использованием UNC-путей. Этот код является частью модуля, который был первоначально написан на С++, но теперь преобразован в С#. Версия С# значительно медленнее. Я решил, что вызов для открытия файла учитывает почти всю разницу в производительности. Используя WireShark, я обнаружил, что это связано с тем, что вызов System.IO.File.Open делает гораздо больше сетевых запросов SMB, чем аналогичный код на С++.

Код С++ выполняет этот вызов:

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

Код С# выполняет этот вызов:

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-запросы? Есть ли способ изменить этот код, чтобы избежать всех этих дополнительных запросов?

4b9b3361

Ответ 1

Короче говоря, вызовы new FileStream() и new FileStream() для File.Open содержат много вызовов:

  • NormalisePath.

    String filePath = Path.NormalizePath(path, true, maxPath); // fullCheck: true
    

приводит к этому коду:

1.a: Получить полный путь:

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

GetFullPathName() вызывает Win32Native.GetFullPathName один или два раза (в зависимости от длины результирующего пути).

1б. Попытка расширить короткий путь. Ваш путь содержит ~ char, поэтому он выглядит как кандидат для расширения :

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

в результате получится Win32Native.GetLongPathName() .

  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. Откройте fileStream (floppy наносит ответный удар;)):

     // 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()

Не все из них приведут к запросу smb, но некоторые из них будут делать. Я попытался воспроизвести частые запросы путем отладки с исходным пошаговым руководством (здесь для включения отладки источника .net) и проверки журнала после каждого шага. Репозиты больше похожи на ваш первый список. Если вам действительно интересно найти реальную проблему, вам придется сделать это самостоятельно.

UPD Обратите внимание, что я проверил текущее поведение (.net 4.5.2). Он был изменен несколько раз, так как 2.0 (например, FileIOPermission.Demand() изначально был вызван для полностью доверенного кода), поэтому он зависит:)

Ответ 2

У меня нет конкретного ответа на вопрос о том, почему реализация .NET настолько чата, но это будет связано с реализацией System.IO.FileStream, поскольку все, что делает File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);, это передача параметров в конструктор FileStream.

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

Изменение поведения FileStream означает, что вам в основном придется повторно реализовать класс FileStream, который потребует больших усилий.

Другой более простой альтернативой было бы создание родной оболочки, которая вызывает код С++, который вы дали. Затем вызовите родную оболочку из вашего кода С#.