Асинхронный файловый ввод-вывод в .Net - PullRequest
7 голосов
/ 18 сентября 2008

Я создаю игрушечную базу данных в C #, чтобы узнать больше о компиляторе, оптимизаторе и технологии индексирования.

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

Вот несколько вариантов и проблем, с которыми я сталкивался:

  1. Используйте System.IO.FileStream и метод BeginRead

    Но позиция в файле не является аргументом для BeginRead, это свойство FileStream (устанавливается с помощью метода Seek), поэтому я могу выдавать только один запрос за раз и должны заблокировать поток на время. (Или я? В документации неясно, что произойдет, если я удерживаю блокировку только между вызовами Seek и BeginRead, но снимаю ее до вызова EndRead. Кто-нибудь знает?) Я знаю, как это сделать, Я просто не уверен, что это лучший способ.

  2. Кажется, есть другой способ, сосредоточенный вокруг структуры System.Threading.Overlapped и P \ Invoke для функции ReadFileEx в kernel32.dll.

    К сожалению, не хватает примеров, особенно в управляемых языках. Этот маршрут (если его вообще можно заставить работать), по-видимому, также включает метод ThreadPool.BindHandle и потоки завершения ввода-вывода в пуле потоков. У меня складывается впечатление, что это санкционированный способ работы с этим сценарием в Windows, но я его не понимаю и не могу найти точку входа в документацию, полезную для непосвященных.

  3. Что-то еще?

  4. В комментарии jacob предлагает создать новое FileStream для каждого чтения в полете.

  5. Считать весь файл в память.

    Это будет работать, если база данных будет маленькой. Кодовая база небольшая, и есть много других недостатков, но сама база данных не так. Я также хочу быть уверен, что я веду всю бухгалтерию, необходимую для работы с большой базой данных (которая оказывается огромной частью сложности: подкачка страниц, внешняя сортировка, ...), и я боюсь, что это может быть слишком легко обмануть случайно.

Редактировать

Разъяснение того, почему я с подозрением отношусь к решению 1: удержание одной блокировки на всем пути от BeginRead до EndRead означает, что мне нужно заблокировать любого, кто хочет инициировать чтение, только потому, что выполняется другое чтение. Это кажется неправильным, потому что поток, инициирующий новое чтение, может (в целом) выполнить еще некоторую работу до того, как результаты станут доступны. (На самом деле, просто написание этого текста заставило меня придумать новое решение, я поставил новый ответ.)

Ответы [ 4 ]

5 голосов
/ 18 сентября 2008

Я не уверен, что вижу, почему вариант 1 не будет работать для вас. Имейте в виду, что у вас не может быть двух разных потоков, пытающихся использовать один и тот же FileStream одновременно - это определенно вызовет у вас проблемы. BeginRead / EndRead предназначен для того, чтобы ваш код продолжал выполняться, пока выполняется потенциально дорогостоящая операция ввода-вывода, а не для обеспечения какого-либо многопоточного доступа к файлу.

Поэтому я бы посоветовал вам поискать, а затем начать читать.

3 голосов
/ 18 сентября 2008

То, что мы сделали, это написали небольшой слой вокруг портов завершения ввода-вывода, ReadFile и статуса GetQueuedCompletion в C ++ / CLI, а затем перезвонили в C # после завершения операции. Мы выбрали этот маршрут вместо BeginRead и шаблона асинхронной операции c #, чтобы обеспечить больший контроль над буферами, используемыми для чтения из файла (или сокета). Это было довольно большое увеличение производительности по сравнению с чисто управляемым подходом, который выделяет новый байт [] в куче при каждом чтении.

Кроме того, есть много более полных примеров C ++ использования портов завершения ввода-вывода на межсетевых страницах

1 голос
/ 18 сентября 2008

Что если вы сначала загрузили ресурс (данные файла или что-то еще) в память, а затем распределили его между потоками? Так как это маленькая БД. - у вас не будет столько проблем для решения.

0 голосов
/ 18 сентября 2008

Использовать подход № 1, , но

  1. Когда поступит запрос, заблокируйте A. Используйте его для защиты очереди ожидающих запросов на чтение. Добавьте его в очередь и верните новый асинхронный результат. Если это приводит к первому добавлению в очередь, вызовите шаг 2, прежде чем вернуться. Отпустите замок A перед возвратом.

  2. Когда чтение завершено (или вызвано на шаге 1), возьмите блокировку А. Используйте ее для защиты выталкивания запроса на чтение из очереди. Возьмите замок B. Используйте его для защиты последовательности Seek -> BeginRead -> EndRead. Снимите блокировку B. Обновите результат асинхронности, созданный на шаге 1 для этой операции чтения. (Поскольку операция чтения завершена, вызовите ее снова.)

Это решает проблему не блокирования какого-либо потока, который начинает чтение, только потому, что выполняется другое чтение, но все еще читает последовательности, чтобы текущая позиция файлового потока не была испорчена.

...