Требования к синхронизации для FileStream. (Начало / Конец) (Чтение / Запись) - PullRequest
9 голосов
/ 02 апреля 2010

Допустим ли следующий шаблон многопоточных вызовов для .Net FileStream ?

Несколько потоков, вызывающих такой метод:

ulong offset = whatever; // different for each thread
byte[] buffer = new byte[8192];
object state = someState; // unique for each call, hence also for each thread

lock(theFile)
{
    theFile.Seek(whatever, SeekOrigin.Begin);
    IAsyncResult result = theFile.BeginRead(buffer, 0, 8192, AcceptResults, state);
}

if(result.CompletedSynchronously)
{
    // is it required for us to call AcceptResults ourselves in this case?
    // or did BeginRead already call it for us, on this thread or another?
}

Где AcceptResults:

void AcceptResults(IAsyncResult result)
{
    lock(theFile)
    {
         int bytesRead = theFile.EndRead(result);

         // if we guarantee that the offset of the original call was at least 8192 bytes from
         // the end of the file, and thus all 8192 bytes exist, can the FileStream read still
         // actually read fewer bytes than that?

         // either:
         if(bytesRead != 8192)
         {
             Panic("Page read borked");
         }

         // or:
         // issue a new call to begin read, moving the offsets into the FileStream and
         // the buffer, and decreasing the requested size of the read to whatever remains of the buffer
    }
}

Я запутался, потому что документация мне неясна. Например, класс FileStream говорит:

Любые открытые статические члены этого типа являются потокобезопасными. Любые члены экземпляра не гарантированно являются потокобезопасными.

Но документация для BeginRead, похоже, предполагает наличие нескольких запросов на чтение в полете:

Несколько одновременных асинхронных запросов приводят к неопределенности порядка выполнения запроса.

Разрешено ли многократное чтение в полете или нет? Пишет? Является ли этот способ безопасным для определения местоположения потока Position между вызовом Seek и вызовом BeginRead? Или эту блокировку нужно удерживать вплоть до EndRead, следовательно, только одно чтение или запись в полете за раз?

Я понимаю, что обратный вызов будет происходить в другом потоке, и моя обработка state, buffer обрабатывает это таким образом, который допускает множественное чтение в полете.

Далее, кто-нибудь знает, где в документации найти ответы на эти вопросы? Или статья написана кем-то в курсе? Я искал и ничего не могу найти.

Соответствующая документация:

FileStream класс
Метод поиска
Метод BeginRead
EndRead
Интерфейс IAsyncResult

Редактировать с новой информацией

Быстрая проверка с помощью Reflector показывает, что BeginRead фиксирует позицию потока в состоянии для каждого вызова (некоторые поля структуры NativeOverlapped). Похоже, что EndRead не проверяет позицию потока, по крайней мере, не совсем очевидным образом. Это не является окончательным, очевидно, потому что это может быть неочевидным способом или может не поддерживаться базовым нативным API.

1 Ответ

1 голос
/ 03 апреля 2010

Да, документация отрывочная.К сожалению, нет подсказки для лучших документов.

РЕДАКТИРОВАТЬ: На самом деле книга Джо Даффи о параллельном программировании в Windows имеет главу 8 APM, в которой объясняется асинхронный API, IAsyncResult и тому подобное (хорошая книга и автор).Тем не менее, фундаментальная проблема здесь заключается в том, что MSDN говорит, что переменные экземпляра не являются потокобезопасными, поэтому необходима соответствующая синхронизация.

То есть у вас есть несколько потоков, запускающих BeginRead в одном и том же экземпляре файла?Однако на странице BeginRead это упоминается: «EndRead должен вызываться ровно один раз для каждого вызова BeginRead. Если не завершить процесс чтения перед началом другого чтения, это может привести к нежелательному поведению, такому как взаимоблокировка».Также вы вызываете Seek для объекта theFile, в то время как другие потоки могут выполнять свои обратные вызовы BeginRead.Совсем не безопасно.

...