Asynchrone bestandstoegang (C#)

U kunt de asynchrone functie gebruiken om toegang te krijgen tot bestanden. Met behulp van de asynchrone functie kunt u asynchrone methoden aanroepen zonder callbacks te gebruiken of uw code te splitsen over meerdere methoden of lambda-expressies. Als u synchrone code asynchroon wilt maken, roept u gewoon een asynchrone methode aan in plaats van een synchrone methode en voegt u enkele trefwoorden toe aan de code.

U kunt de volgende redenen overwegen om asynchroon toe te voegen aan bestandstoegangsoproepen:

  • Asynchroon maakt UI-toepassingen responsiever omdat de UI-thread waarmee de bewerking wordt gestart, andere werkzaamheden kan uitvoeren. Als de UI-thread code moet uitvoeren die lang duurt (bijvoorbeeld meer dan 50 milliseconden), kan de gebruikersinterface vastlopen totdat de I/O is voltooid en de UI-thread opnieuw toetsenbord- en muisinvoer en andere gebeurtenissen kan verwerken.
  • Asynchronisatie verbetert de schaalbaarheid van ASP.NET en andere servertoepassingen door de noodzaak van threads te verminderen. Als de toepassing een toegewezen thread per antwoord gebruikt en er duizend aanvragen tegelijk worden verwerkt, zijn er duizend threads nodig. Asynchrone bewerkingen hoeven vaak geen thread te gebruiken tijdens het wachten. Ze gebruiken de bestaande I/O-voltooiingsthread kort aan het einde.
  • De latentie van een bestandstoegangsbewerking kan onder de huidige omstandigheden erg laag zijn, maar de latentie kan in de toekomst aanzienlijk toenemen. Een bestand kan bijvoorbeeld worden verplaatst naar een server die zich over de hele wereld bevindt.
  • De extra overhead voor het gebruik van de Async-functie is klein.
  • Asynchrone taken kunnen eenvoudig parallel worden uitgevoerd.

De juiste klassen gebruiken

In de eenvoudige voorbeelden in dit onderwerp ziet u File.WriteAllTextAsync en File.ReadAllTextAsync. Voor een nauwkeurige controle over de I/O-bewerkingen van het bestand gebruikt u de FileStream klasse. Deze heeft een optie die ervoor zorgt dat asynchrone I/O op het niveau van het besturingssysteem plaatsvindt. Door deze optie te gebruiken, kunt u in veel gevallen voorkomen dat een threadgroepthread wordt geblokkeerd. Als u deze optie wilt inschakelen, geeft u het useAsync=true argument of options=FileOptions.Asynchronous op in de constructor-aanroep.

U kunt deze optie niet gebruiken met StreamReader en StreamWriter als u ze rechtstreeks opent door een bestandspad op te geven. U kunt deze optie echter gebruiken als u hen opgeeft Stream dat de FileStream klasse is geopend. Asynchrone aanroepen zijn sneller in UI-apps, zelfs als een threadgroepthread is geblokkeerd, omdat de UI-thread niet wordt geblokkeerd tijdens het wachten.

Tekst schrijven

In de volgende voorbeelden wordt tekst naar een bestand geschreven. Bij elke await-instructie wordt de methode onmiddellijk afgesloten. Wanneer de I/O van het bestand is voltooid, wordt de methode hervat bij de instructie die volgt op de instructie await. De asynchrone wijziging bevindt zich in de definitie van methoden die de instructie await gebruiken.

Eenvoudig voorbeeld

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

    await File.WriteAllTextAsync(filePath, text);
}

Voorbeeld van eindig besturingselement

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

Het oorspronkelijke voorbeeld heeft de instructie await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);, wat een samentrekking is van de volgende twee instructies:

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

De eerste instructie retourneert een taak en zorgt ervoor dat bestandsverwerking wordt gestart. De tweede instructie met de await zorgt ervoor dat de methode onmiddellijk wordt afgesloten en een andere taak retourneert. Wanneer de bestandsverwerking later is voltooid, keert de uitvoering terug naar de instructie die volgt op de await.

Tekst voorlezen

In de volgende voorbeelden wordt tekst uit een bestand gelezen.

Eenvoudig voorbeeld

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

    Console.WriteLine(text);
}

Voorbeeld van eindig besturingselement

De tekst wordt gebufferd en in dit geval in een StringBuildergeplaatst. In tegenstelling tot in het vorige voorbeeld produceert de evaluatie van de wacht een waarde. De ReadAsync methode retourneert eenInt32<>Task , zodat de evaluatie van de wacht een Int32 waarde numRead produceert nadat de bewerking is voltooid. Zie Asynchrone retourtypen (C#) voor meer informatie.

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

Parallelle asynchrone I/O

In de volgende voorbeelden ziet u parallelle verwerking door 10 tekstbestanden te schrijven.

Eenvoudig voorbeeld

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

Voorbeeld van eindig besturingselement

Voor elk bestand retourneert de WriteAsync methode een taak die vervolgens wordt toegevoegd aan een lijst met taken. De await Task.WhenAll(tasks); instructie sluit de methode af en hervat binnen de methode wanneer de bestandsverwerking voor alle taken is voltooid.

In het voorbeeld worden alle FileStream exemplaren in een finally blok gesloten nadat de taken zijn voltooid. Als elk FileStream item in plaats daarvan is gemaakt in een using -instructie, kan de FileStream worden verwijderd voordat de taak is voltooid.

Elke prestatieverbeteringen zijn bijna volledig afkomstig van de parallelle verwerking en niet van de asynchrone verwerking. De voordelen van asynchroonheid zijn dat er niet meerdere threads worden gekoppeld en dat de thread van de gebruikersinterface niet wordt gekoppeld.

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

Wanneer u de WriteAsync methoden en ReadAsync gebruikt, kunt u een CancellationTokenopgeven die u kunt gebruiken om de bewerking halverwege de stream te annuleren. Zie Annulering in beheerde threads voor meer informatie.

Zie ook