IO arquivo assíncrono em .Net
-
01-07-2019 - |
Pergunta
Eu estou construindo um banco de dados de brinquedo em C # para aprender mais sobre compilador, otimizador, e tecnologia de indexação.
Eu quero manter o máximo de paralelismo entre (pelo menos ler) pedidos de trazendo páginas para o pool de buffer, mas estou confuso sobre a melhor forma de fazer isso em .NET.
Aqui estão algumas opções e os problemas que eu me deparei com cada um:
-
Use
System.IO.FileStream
eo métodoBeginRead
Mas, a posição no arquivo não é um argumento para
BeginRead
, é uma propriedade doFileStream
(definido através do métodoSeek
), então eu só pode emitir um pedido de cada vez e tem que bloquear o fluxo para a duração. (Ou eu? A documentação não é clara sobre o que aconteceria se eu segurei o bloqueio apenas entre as chamadasSeek
eBeginRead
mas liberado antes de chamarEndRead
. Alguém sabe?) Eu sei como fazer isso, eu não sou apenas certo é o melhor caminho. -
Parece haver uma outra maneira, centrada em torno da estrutura
System.Threading.Overlapped
e P \ Chamar para a funçãoReadFileEx
em kernel32.dll.Infelizmente, há uma escassez de amostras, especialmente em idiomas gerenciados. Esta rota (se ele pode ser feito para trabalhar em tudo), aparentemente, também envolve o método
ThreadPool.BindHandle
e os fios de conclusão IO no pool de threads. Tenho a impressão de que este é o caminho sancionada de lidar com esse cenário sob janelas, mas eu não entendo isso e não consigo encontrar um ponto de entrada para a documentação que é útil para os não iniciados. -
Algo mais?
-
Em um comentário, jacob sugere a criação de um novo
FileStream
para cada leitura em vôo. -
Leia o arquivo inteiro na memória.
Isso poderia funcionar se o banco de dados era pequeno. A base de código é pequeno, e há uma abundância de outras ineficiências, mas o banco de dados em si não é. Eu também quero ter certeza que eu estou fazendo toda a contabilidade necessária para lidar com um grande banco de dados (o que acaba por ser uma grande parte da complexidade: paginação, ordenação externa, ...) e eu estou preocupado que pode ser muito fácil de enganar acidentalmente.
Editar
Clarificação de porque eu sou suspeito com uma solução de 1: segurando um único bloqueio todo o caminho de BeginRead para EndRead significa que eu preciso para bloquear qualquer pessoa que queira iniciar uma leitura só porque outra leitura está em andamento. Que se sente mal, porque o segmento de iniciar a nova leitura pode ser capaz (em geral) para fazer mais algum trabalho antes que os resultados se tornam disponíveis. (Na verdade, apenas escrevendo isso me levou a pensar em uma nova solução, eu coloquei como uma nova resposta.)
Solução
O que fizemos foi para escrever uma pequena camada em torno de portas de conclusão de E / S, ReadFile, e status GetQueuedCompletion em C ++ / CLI, e em seguida, chamar de volta para C # quando a operação foi concluída. Nós escolhemos esta rota ao longo BeginRead eo # assíncrono padrão de operação c para fornecer mais controle sobre os buffers usados ??para ler a partir do arquivo (ou socket). Este foi um ganho muito grande desempenho ao longo da abordagem puramente gerenciado que aloca novo byte [] na pilha com cada leitura.
Além disso, há muito mais completa C ++ exemplos do uso de portas IO de conclusão para fora nas interwebs
Outras dicas
Eu não estou certo que eu vejo por que a opção 1 não iria trabalhar para você. Tenha em mente que você não pode ter dois tópicos diferentes tentando usar o mesmo FileStream ao mesmo tempo - isso irá certamente causar-lhe problemas. BeginRead / EndRead destina-se a deixar o seu código de continuar a executar enquanto a operação potencialmente caro IO leva lugares, para não permitir que algum tipo de acesso multi-threaded para um arquivo.
Então, eu sugeriria que você procura e, em seguida, fazer uma BeginRead.
E se você carregou o recurso (dados de arquivo ou qualquer outro) para a memória primeiro e depois compartilhá-la entre segmentos? Uma vez que é um pequeno db. -. Você não vai ter tantos problemas para lidar com
Use abordagem nº 1, e
-
Quando uma solicitação chega, take bloqueio A. usá-lo para proteger uma fila de pedidos pendentes ler. Adicioná-lo à fila e retornar algum novo resultado assíncrona. Se isso resulta na primeira adição à fila, chamar o passo 2 antes de retornar. bloqueio Release A antes de retornar.
-
Quando A completa ler (ou chamados a passo 1), tome bloqueio A. usá-lo para proteger estalando um pedido de leitura da fila. Tome bloqueio B. usá-lo para proteger o
Seek
->BeginRead
-> sequênciaEndRead
. bloqueio de liberação B. atualizar o resultado assíncrono criado por etapa 1 para esta operação de leitura. (Uma vez que uma operação de leitura concluída, chamar isso de novo).
Isto resolve o problema de não bloquear qualquer segmento que começa uma leitura só porque outra leitura está em andamento, mas as sequências ainda lê modo que a posição atual do fluxo de arquivo não ficar confuso.