Asynchrone bestandstoegang (C#)

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

Overweeg om, om deze redenen, asynchronie toe te voegen aan aanroepen voor bestandstoegang.

  • Asynchrony maakt UI-toepassingen responsiefer omdat de UI-thread waarmee de bewerking wordt gestart, ander werk kan uitvoeren. Als de UI-thread code moet uitvoeren die lang duurt (bijvoorbeeld meer dan 50 milliseconden), wordt de gebruikersinterface mogelijk geblokkeerd totdat de I/O is voltooid en kan de UI-thread opnieuw toetsenbord- en muisinvoer en andere gebeurtenissen verwerken.
  • Asynchroon 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 duizend aanvragen tegelijkertijd worden verwerkt, zijn er duizend threads nodig. Asynchrone bewerkingen hoeven vaak geen thread te gebruiken tijdens de wachttijd. Ze gebruiken kort aan het einde de bestaande I/O-voltooiingsthread.
  • De latentie van een bestandstoegangsbewerking kan erg laag zijn onder de huidige omstandigheden, maar de latentie kan in de toekomst aanzienlijk toenemen. Een bestand kan bijvoorbeeld worden verplaatst naar een server over de hele wereld.
  • De extra overhead voor het gebruik van de Async-functie is klein.
  • Meerdere asynchrone I/O-bewerkingen kunnen worden uitgevoerd zonder de aanroepende thread te blokkeren.

De juiste klassen gebruiken

De eenvoudige voorbeelden in dit onderwerp demonstreren File.WriteAllTextAsync en File.ReadAllTextAsync. Voor een nauwkeurige controle over de I/O-bewerkingen van het bestand gebruikt u de FileStream klasse, die een optie heeft die ervoor zorgt dat asynchrone I/O op besturingssysteemniveau plaatsvindt. Met deze optie kunt u in veel gevallen voorkomen dat een thread uit de threadpool wordt geblokkeerd. Als u deze optie wilt inschakelen, geeft u het useAsync=true of options=FileOptions.Asynchronous argument 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 een Stream opgeeft die de klasse FileStream heeft geopend. Asynchrone oproepen zijn sneller in UI-toepassingen, zelfs als een thread in de threadpool is geblokkeerd, omdat de UI-thread tijdens de wachttijd niet wordt geblokkeerd.

Tekst schrijven

In de volgende voorbeelden wordt tekst naar een bestand geschreven. Bij elke await-instructie wordt de methode onmiddellijk afgesloten. Wanneer de bestands-I/O is voltooid, wordt de methode hervat bij de instructie die volgt op de await-instructie. De async-modifier wordt gebruikt in de definitie van methodes die de await-instructie 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);, een samentrekking 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 await zorgt ervoor dat de methode onmiddellijk wordt verlaten en een andere taak wordt geretourneerd. Wanneer de bestandsverwerking is voltooid, gaat de uitvoering verder met de instructie die volgt op de await.

Tekst lezen

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 gedeponeerd in een StringBuilder. In tegenstelling tot het vorige voorbeeld produceert de evaluatie van await een waarde. De ReadAsync methode retourneert een Task<Int32>, zodat de evaluatie van de await een Int32 waarde numRead oplevert 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();
}

Meerdere asynchrone I/O-bewerkingen

In de volgende voorbeelden worden meerdere asynchrone schrijfbewerkingen gestart. De runtime plaatst deze bewerkingen in wachtrijen, en de onderliggende implementatie kan, afhankelijk van het platform en de configuratie, gebruikmaken van asynchrone I/O of threadpoolthreads van het besturingssysteem (OS). Daardoor is de daadwerkelijke gelijktijdigheid afhankelijk van het besturingssysteem en de hardware.

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 wordt toegevoegd aan een lijst met taken. De await Task.WhenAll(tasks); instructie verlaat de methode en hervat de uitvoering 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 in plaats daarvan in een using instructie is gemaakt, kan het FileStream worden verwijderd voordat de taak is voltooid.

De asynchrone benadering voorkomt dat de oproepende thread wordt geblokkeerd terwijl de I/O nog in afwachting is. In veel gevallen zijn doorvoerverbeteringen afhankelijk van het besturingssysteem, de hardware en, op sommige platforms, .NET-runtimegedrag, zoals limieten voor threadpools en planning.

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 en ReadAsync methoden gebruikt, kunt u een CancellationToken opdracht opgeven om de bewerking halverwege de stream te annuleren. Zie Annulering in beheerde threads voor meer informatie.

Zie ook