Partilhar via


Acesso a ficheiros assíncronos (C#)

Pode utilizar a funcionalidade assíncrona para aceder a ficheiros. Ao utilizar a funcionalidade assíncrona, pode chamar métodos assíncronos sem utilizar chamadas de retorno ou dividir o código em vários métodos ou expressões lambda. Para tornar o código síncrono assíncrono, basta chamar um método assíncrono em vez de um método síncrono e adicionar algumas palavras-chave ao código.

Pode considerar os seguintes motivos para adicionar assíncrona a chamadas de acesso a ficheiros:

  • A assíncrona torna as aplicações de IU mais reativas porque o thread de IU que inicia a operação pode realizar outros trabalhos. Se o thread de IU tiver de executar o código que demora muito tempo (por exemplo, mais de 50 milissegundos), a IU poderá fixar até que a E/S esteja concluída e o thread de IU possa processar novamente a entrada do teclado e do rato e outros eventos.
  • A assíncrona melhora a escalabilidade do ASP.NET e de outras aplicações baseadas no servidor ao reduzir a necessidade de threads. Se a aplicação utilizar um thread dedicado por resposta e mil pedidos estiverem a ser processados em simultâneo, são necessários mil threads. Muitas vezes, as operações assíncronas não precisam de utilizar um thread durante a espera. Utilizam o thread de conclusão de E/S existente brevemente no final.
  • A latência de uma operação de acesso a ficheiros pode ser muito baixa nas condições atuais, mas a latência pode aumentar significativamente no futuro. Por exemplo, um ficheiro pode ser movido para um servidor que esteja em todo o mundo.
  • A sobrecarga adicionada da utilização da funcionalidade Assíncrona é pequena.
  • As tarefas assíncronas podem ser facilmente executadas em paralelo.

Utilizar classes adequadas

Os exemplos simples neste tópico demonstram File.WriteAllTextAsync e File.ReadAllTextAsync. Para controlar bem as operações de E/S do ficheiro, utilize a FileStream classe, que tem uma opção que faz com que a E/S assíncrona ocorra ao nível do sistema operativo. Ao utilizar esta opção, pode evitar bloquear um thread de conjunto de threads em muitos casos. Para ativar esta opção, especifique o useAsync=true argumento ou options=FileOptions.Asynchronous na chamada do construtor.

Não pode utilizar esta opção com StreamReader e StreamWriter se as abrir diretamente ao especificar um caminho de ficheiro. No entanto, pode utilizar esta opção se lhes indicar que Stream a classe abriu FileStream . As chamadas assíncronas são mais rápidas nas aplicações de IU, mesmo que um thread de conjunto de threads esteja bloqueado, porque o thread de IU não é bloqueado durante a espera.

Escrever texto

Os exemplos seguintes escrevem texto num ficheiro. Em cada instrução de espera, o método sai imediatamente. Quando a E/S do ficheiro estiver concluída, o método é retomado na instrução que se segue à instrução await. O modificador assíncrono está na definição de métodos que utilizam a instrução await.

Exemplo simples

public async Task SimpleWriteAsync()
{
    string filePath = "simple.txt";
    string text = $"Hello World";

    await File.WriteAllTextAsync(filePath, text);
}

Exemplo de controlo finito

public async Task ProcessWriteAsync()
{
    string filePath = "temp.txt";
    string text = $"Hello World{Environment.NewLine}";

    await WriteTextAsync(filePath, text);
}

async Task WriteTextAsync(string filePath, string text)
{
    byte[] encodedText = Encoding.Unicode.GetBytes(text);

    using var sourceStream =
        new FileStream(
            filePath,
            FileMode.Create, FileAccess.Write, FileShare.None,
            bufferSize: 4096, useAsync: true);

    await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
}

O exemplo original tem a instrução await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);, que é uma contração das duas instruções seguintes:

Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
await theTask;

A primeira instrução devolve uma tarefa e faz com que o processamento de ficheiros seja iniciado. A segunda instrução com a aguardação faz com que o método saia imediatamente e devolva uma tarefa diferente. Quando o processamento do ficheiro for concluído mais tarde, a execução regressa à instrução que se segue à espera.

Ler texto

Os exemplos seguintes leem texto de um ficheiro.

Exemplo simples

public async Task SimpleReadAsync()
{
    string filePath = "simple.txt";
    string text = await File.ReadAllTextAsync(filePath);

    Console.WriteLine(text);
}

Exemplo de controlo finito

O texto está em memória intermédia e, neste caso, colocado num StringBuilder. Ao contrário do exemplo anterior, a avaliação da espera produz um valor. O ReadAsync método devolve um Task<>Int32, pelo que a avaliação da espera produz um Int32 valor numRead após a conclusão da operação. Para obter mais informações, veja Async Return Types (C#).

public async Task ProcessReadAsync()
{
    try
    {
        string filePath = "temp.txt";
        if (File.Exists(filePath) != false)
        {
            string text = await ReadTextAsync(filePath);
            Console.WriteLine(text);
        }
        else
        {
            Console.WriteLine($"file not found: {filePath}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

async Task<string> ReadTextAsync(string filePath)
{
    using var sourceStream =
        new FileStream(
            filePath,
            FileMode.Open, FileAccess.Read, FileShare.Read,
            bufferSize: 4096, useAsync: true);

    var sb = new StringBuilder();

    byte[] buffer = new byte[0x1000];
    int numRead;
    while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
    {
        string text = Encoding.Unicode.GetString(buffer, 0, numRead);
        sb.Append(text);
    }

    return sb.ToString();
}

E/S assíncrona paralela

Os exemplos seguintes demonstram o processamento paralelo ao escrever 10 ficheiros de texto.

Exemplo simples

public async Task SimpleParallelWriteAsync()
{
    string folder = Directory.CreateDirectory("tempfolder").Name;
    IList<Task> writeTaskList = new List<Task>();

    for (int index = 11; index <= 20; ++ index)
    {
        string fileName = $"file-{index:00}.txt";
        string filePath = $"{folder}/{fileName}";
        string text = $"In file {index}{Environment.NewLine}";

        writeTaskList.Add(File.WriteAllTextAsync(filePath, text));
    }

    await Task.WhenAll(writeTaskList);
}

Exemplo de controlo finito

Para cada ficheiro, o WriteAsync método devolve uma tarefa que é adicionada a uma lista de tarefas. A await Task.WhenAll(tasks); instrução sai do método e é retomada no método quando o processamento de ficheiros é concluído para todas as tarefas.

O exemplo fecha todas as FileStream instâncias num finally bloco após a conclusão das tarefas. Se cada um FileStream tiver sido criado numa using instrução, poderá FileStream ser eliminado antes de a tarefa ser concluída.

Qualquer aumento de desempenho é quase inteiramente do processamento paralelo e não do processamento assíncrono. As vantagens da assíncrona são que não liga vários threads e que não liga o thread da interface de utilizador.

public async Task ProcessMultipleWritesAsync()
{
    IList<FileStream> sourceStreams = new List<FileStream>();

    try
    {
        string folder = Directory.CreateDirectory("tempfolder").Name;
        IList<Task> writeTaskList = new List<Task>();

        for (int index = 1; index <= 10; ++ index)
        {
            string fileName = $"file-{index:00}.txt";
            string filePath = $"{folder}/{fileName}";

            string text = $"In file {index}{Environment.NewLine}";
            byte[] encodedText = Encoding.Unicode.GetBytes(text);

            var sourceStream =
                new FileStream(
                    filePath,
                    FileMode.Create, FileAccess.Write, FileShare.None,
                    bufferSize: 4096, useAsync: true);

            Task writeTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
            sourceStreams.Add(sourceStream);

            writeTaskList.Add(writeTask);
        }

        await Task.WhenAll(writeTaskList);
    }
    finally
    {
        foreach (FileStream sourceStream in sourceStreams)
        {
            sourceStream.Close();
        }
    }
}

Ao utilizar os WriteAsync métodos e ReadAsync , pode especificar um CancellationToken, que pode utilizar para cancelar a operação a meio do fluxo. Para obter mais informações, veja Cancelamento em threads geridos.

Ver também