Asynchronní přístup k souborům (C#)

Pomocí asynchronní funkcionality pro přistupování k souborům můžete volat asynchronní metody, aniž by bylo nutné používat zpětné volání nebo rozdělovat kód mezi více metod či výrazy lambda. Pokud chcete, aby synchronní kód byl asynchronní, zavolejte místo synchronní metody asynchronní metodu a přidejte do kódu několik klíčových slov.

Zvažte přidání asynchronnosti do volání pro přístup k souborům z těchto důvodů:

  • Asynchrony zvyšuje odezvu aplikací uživatelského rozhraní, protože vlákno uživatelského rozhraní, které spouští operaci, může provádět jinou práci. Pokud vlákno uživatelského rozhraní musí spouštět kód, který trvá dlouhou dobu (například více než 50 milisekund), uživatelské rozhraní se může ukotvit, dokud se vstupně-výstupní operace nezačne dokončit a vlákno uživatelského rozhraní může znovu zpracovat vstup klávesnice a myš a další události.
  • Asynchrony zlepšuje škálovatelnost ASP.NET a dalších serverových aplikací tím, že snižuje potřebu vláken. Pokud aplikace používá vyhrazené vlákno na odpověď a současně zpracovává tisíce požadavků, je potřeba tisíc vláken. Asynchronní operace často během čekání nepotřebují používat vlákno. Použijí existující vlákno pro doplňování vstupně-výstupních operací krátce na konci.
  • Latence operace přístupu k souboru může být za aktuálních podmínek velmi nízká, ale latence se může v budoucnu výrazně zvýšit. Soubor se například může přesunout na server, který je po celém světě.
  • Přidaná režie při používání funkce Async je malá.
  • Více asynchronních vstupně-výstupních operací může běžet bez blokování volajícího vlákna.

Použití vhodných tříd

Jednoduché příklady v tomto tématu ukazují File.WriteAllTextAsync a File.ReadAllTextAsync. K jemné kontrole nad vstupně-výstupními operacemi souboru použijte FileStream třídu, která má možnost, která způsobí, že na úrovni operačního systému dojde k asynchronním vstupně-výstupním operacím. Pomocí této možnosti se můžete vyhnout blokování vlákna fondu vláken v mnoha případech. Chcete-li tuto možnost povolit, zadejte argument useAsync=true nebo options=FileOptions.Asynchronous ve volání konstruktoru.

Tuto možnost nemůžete použít s StreamReader a StreamWriter, pokud je otevřete přímo zadáním cesty k souboru. Tuto možnost však můžete použít, pokud jim poskytnete Stream, který otevřela třída FileStream. Asynchronní volání jsou v aplikacích uživatelského rozhraní rychlejší, i když je některé vlákno v fondu vláken blokované, protože vlákno uživatelského rozhraní není během čekání blokované.

Psaní textu

Následující příklady zapisují text do souboru. V každém příkazu await metoda okamžitě ukončí. Po dokončení vstupně-výstupních operací se metoda obnoví v příkazu, který následuje za příkazem await. Modifikátor async je v definici metod, které používají příkaz await.

Jednoduchý příklad

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

    await File.WriteAllTextAsync(filePath, text);
}

Příklad konečného ovládacího prvku

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);
}

Původní příklad obsahuje příkaz await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);, což je kontrakt následujících dvou příkazů:

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

První příkaz vrátí úlohu a způsobí spuštění zpracování souborů. Druhý příkaz await způsobí, že metoda okamžitě ukončí svoji činnost a vrátí jinou úlohu. Po dokončení zpracování souboru se provádění vrátí na příkaz, který následuje za příkazem await.

Čtení textu

Následující příklady čtou text ze souboru.

Jednoduchý příklad

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

    Console.WriteLine(text);
}

Příklad konečného ovládacího prvku

Text je uložen do vyrovnávací paměti a v tomto případě je umístěn do .StringBuilder Na rozdíl od předchozího příkladu vede vyhodnocení await k získání hodnoty. Metoda ReadAsync vrátí hodnotuTask<Int32> , takže vyhodnocení await vytvoří Int32 hodnotu numRead po dokončení operace. Další informace najdete v tématu Asynchronní návratové typy (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();
}

Několik asynchronních vstupně-výstupních operací

Následující příklady spustí několik asynchronních operací zápisu. Modul runtime zařadí tyto operace do fronty a základní implementace může, v závislosti na platformě a konfiguraci, používat asynchronní I/O operace nebo vlákna z fondu vláken operačního systému, takže skutečná souběžnost závisí na operačním systému a hardwaru.

Jednoduchý příklad

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);
}

Příklad konečného ovládacího prvku

Pro každý soubor WriteAsync vrátí metoda úkol, který je přidán do seznamu úkolů. Příkaz await Task.WhenAll(tasks); ukončí metodu a pokračuje v metodě, když je zpracování souboru dokončeno pro všechny úlohy.

Příklad po dokončení úkolů zavře všechny FileStream instance v finally bloku. Pokud by byl každý FileStream vytvořen v příkazu using, FileStream může být zrušen před dokončením úkolu.

Asynchronní přístup zabraňuje blokování volajícího vlákna během čekání na vstupně-výstupní operace. V mnoha případech vylepšení propustnosti závisí na operačním systému, hardwaru a na některých platformách, chování modulu runtime .NET, jako jsou limity fondu vláken a plánování.

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();
        }
    }
}

Při použití WriteAsync metod a ReadAsync metod můžete zadat CancellationToken , aby se operace zrušila uprostřed datového proudu. Další informace najdete v tématu Zrušení ve spravovaných vláknech.

Viz také