Adicionei um novo recurso a um dos meus aplicativos Blazor. Isso permite o upload de arquivos para o armazenamento de blobs em blocos. Isso funciona muito bem. Dei uma olhada para processar memória no Visual Studio. Onde meu aplicativo tinha o tempo todo 80 MB, com todas as suas coisas. Mas quando inicio um upload, vejo que o consumo de memória aumenta em 10-20 MB e não volta. Também aumenta a cada upload de um arquivo. Observe que eu carrego arquivos em blocos de 10 MB, antes eu tinha 100 MB e aumentou 100-200 MB. Então agora não tenho certeza se é vazamento de memória (acho que é) ou se o gc demora muito.
aqui minha função de dentro do serviço para referência
public async Task UploadFileToBlobStorage(Stream stream, string fileName, string containerName,string contentType,IProgress<long> progress,CancellationToken cancellationToken)
{
_logger.LogInformation($"Uploading file {fileName} to container {containerName}");
var blobServiceClient = new BlobServiceClient(new Uri($"https://{accountName}.blob.core.windows.net"), new DefaultAzureCredential());
var containerClient = blobServiceClient.GetBlobContainerClient(containerName);
await containerClient.CreateIfNotExistsAsync(cancellationToken:cancellationToken);
var blockblobClient = containerClient.GetBlockBlobClient(fileName);
var blockIds = new List<string>();
var buffer = new byte[10 * 1024 * 1024]; // 10 MB
int bytesRead;
int blockNumber = 0;
long totalBytesRead = 0;
var blobHttpHeader = new BlobHttpHeaders
{
ContentType = contentType,
ContentDisposition = $"attachment; filename={fileName}",
};
while ((bytesRead = await stream.ReadAsync(buffer.AsMemory( 0, buffer.Length), cancellationToken)) > 0)
{
var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(blockNumber.ToString("d6")));
blockIds.Add(blockId);
using (var blockStream = new MemoryStream(buffer, 0, bytesRead))
{
await blockblobClient.StageBlockAsync(blockId, blockStream,cancellationToken: cancellationToken);
}
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
blockNumber++;
}
await blockblobClient.CommitBlockListAsync(blockIds,httpHeaders:blobHttpHeader,cancellationToken:cancellationToken);
_logger.LogInformation($"File {fileName} uploaded to container {containerName} successfull");
}
O fluxo variável que é passado para a função é um ibrowserfile que foi criado usando
using var stream = _selectedFile.OpenReadStream(maxAllowedSize: long.MaxValue);
Ficarei feliz em receber dicas.
- Alguém vê algum erro grave?
- Talvez não haja engano e minha interpretação esteja errada?
Muito obrigado
À primeira vista, não encontrei um vazamento claro, mas há uma coisa que você precisa considerar: você está criando um buffer de 10 MB (
var buffer = new byte[10 * 1024 * 1024]; // 10 MB
) para cada upload, o que é maior que o tamanho padrão do Large Object Heap (LOH) :Portanto, cada matriz irá diretamente para o LOH, que é coletado apenas com a 2ª geração e não é compactado por padrão:
e
Então, mesmo que a memória não seja realmente necessária (ou seja, nenhuma referência é mantida a ela), ela potencialmente ainda não será coletada/liberada por um bom tempo. É bem provável que seu aplicativo esteja sendo executado no modo GC do servidor, que pode ser bem ganancioso/"preguiçoso" e, em alguns casos, raramente pode executar a coleta de 2ª geração.
Você pode tentar acionar manualmente a coleta e a compactação:
Mas essa não é a abordagem recomendada em casos gerais (embora você possa usá-la se souber o que está fazendo ou para testar vazamento de memória - para ver se a memória é realmente reivindicada pelo GC).
A primeira pergunta que você deve fazer é se esse buffer grande é realmente necessário e tente usar um buffer que se ajuste ao Small Object Heap (SOH), ou seja, menos de 85.000 bytes (eu sugeriria muito menos). Se por algum motivo essa não for uma abordagem que você possa usar - você pode considerar usar a abordagem de pooling (ou seja,
ArrayPool<T>
), mas ela ainda pode alocar dependendo do número de uploads simultâneos. Finalmente, você pode brincar com as configurações de GC, especialmente o Large object heap threshold , mas eu sugeriria ir com a primeira opção ainda (ou seja, usar um buffer menor).Passando dos comentários:
para leitores de acompanhamento. Acabamos com o código funcional acima, mas com uma melhoria. O código funciona como um encanto para muitos usuários que carregam bigfiles.
Observe "writable:false" no fluxo de memória.
Divirta-se muito