Aszinkron fájlhozzáférés (C#)

Ha az aszinkron funkcióval fájlokhoz fér hozzá, visszahívások használata nélkül hívhat be aszinkron metódusokat, vagy feloszthatja a kódot több metódusra vagy lambdakifejezésre. A szinkron kód aszinkronvá alakításához hívjon aszinkron metódust szinkron metódus helyett, és adjon hozzá néhány kulcsszót a kódhoz.

Az alábbi okokból érdemes aszinkron módon hozzáadni a fájlelérési hívásokat:

  • Az aszinkronizálás rugalmasabbá teszi a felhasználói felületi alkalmazásokat, mert a műveletet megnyitó felhasználói felületi szál más műveleteket is végrehajthat. Ha a felhasználói felületi szálnak hosszú ideig tartó kódot kell végrehajtania (például több mint 50 ezredmásodpercet), a felhasználói felület lefagyhat, amíg az I/O be nem fejeződik, és a felhasználói felület szála újra feldolgozhatja a billentyűzet- és egérbemenetet és egyéb eseményeket.
  • Az aszinkronizálás javítja a ASP.NET és más kiszolgálóalapú alkalmazások méretezhetőségét azáltal, hogy csökkenti a szálak szükségességét. Ha az alkalmazás válaszonként dedikált szálat használ, és egyszerre ezer kérést kezelnek, ezer szálra van szükség. Az aszinkron műveleteknek gyakran nem kell szálat használniuk a várakozás során. A végén rövid ideig használják a meglévő I/O-befejezési szálat.
  • A fájlelérési művelet késése a jelenlegi körülmények között nagyon alacsony lehet, de a késés a jövőben jelentősen megnőhet. Előfordulhat például, hogy egy fájl egy olyan kiszolgálóra kerül, amely a világ minden részén található.
  • Az Async szolgáltatás használatának többletterhelése kicsi.
  • Több aszinkron I/O-művelet is futtatható a hívó szál blokkolása nélkül.

Megfelelő osztályok használata

A témakör egyszerű példái bemutatják File.WriteAllTextAsync és a File.ReadAllTextAsync. A fájl I/O-műveleteinek finom vezérléséhez használja az FileStream osztályt, amely az aszinkron I/O-t az operációs rendszer szintjén okozza. Ezzel a beállítással számos esetben elkerülheti a szálkészletbeli szál blokkolását. A beállítás engedélyezéséhez adja meg a useAsync=true vagy a options=FileOptions.Asynchronous argumentumot a konstruktorhívásban.

Ezt a beállítást nem használhatja StreamReader és StreamWriter esetén, ha közvetlenül megnyitja őket egy fájl elérési útjának megadásával. Ezt a lehetőséget azonban akkor használhatja, ha biztosít nekik egy, az FileStream osztály által megnyitott Stream. Az aszinkron hívások akkor is gyorsabbak a felhasználói felületen futó alkalmazásokban, ha egy szál az szálkészletben blokkolva van, mert a felhasználói felület szála nem lesz blokkolva a várakozás során.

Szöveg írása

Az alábbi példák szöveget írnak egy fájlba. Minden várakozási utasításnál a metódus azonnal megszakítja a végrehajtását. A fájl I/O-jának befejezése után a metódus a várakozási utasítást követő utasításban folytatódik. Az aszinkron módosító a await utasítást használó metódusok definíciójában szerepel.

Egyszerű példa

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

    await File.WriteAllTextAsync(filePath, text);
}

Véges vezérlő példa

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

Az eredeti példában az utasítás await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);szerepel, amely a következő két utasítás összevonása:

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

Az első utasítás egy feladatot ad vissza, és elindítja a fájlfeldolgozást. A második await utasítás hatására a metódus azonnal kilép, és egy másik feladatot ad vissza. Amikor a fájlfeldolgozás később befejeződik, a végrehajtás visszatér a várakozást követő utasításhoz.

Szöveg olvasása

Az alábbi példák szövegeket olvasnak egy fájlból.

Egyszerű példa

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

    Console.WriteLine(text);
}

Példa a véges vezérlőről

A szöveg pufferelve van, és ebben az esetben egy StringBuilder-be helyezve van. Az előző példától eltérően a várakozás kiértékelése értéket hoz létre. A ReadAsync metódus egyTask<Int32> értéket ad vissza, így a várakozás kiértékelése a művelet befejezése után értéket Int32 hoz létre.numRead További információt az Async Return Types (C#) című témakörben talál.

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

Több aszinkron I/O-művelet

Az alábbi példák több aszinkron írási műveletet indítanak el. A futtatókörnyezet várólistára állítja ezeket a műveleteket, és az alapul szolgáló implementáció a platformtól és konfigurációtól függően használhat operációsrendszer-aszinkron I/O- vagy szálkészlet-szálakat, így a tényleges egyidejűség az operációs rendszertől és a hardvertől függ.

Egyszerű példa

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

Véges vezérlés példája

A WriteAsync metódus minden fájlhoz visszaad egy feladatot, amely hozzáteszi a feladatokat a tevékenységek listájához. Az await Task.WhenAll(tasks); utasítás kilép a metódusból, és folytatódik a metóduson belül, amikor a fájlfeldolgozás befejeződött az összes feladat esetében.

A példa bezárja a tevékenységek befejezése után az összes FileStream példányt egy finally blokkban. Ha ezek FileStream helyett egy using utasításban jöttek létre, a FileStream művelet befejezése előtt lehet, hogy a rendszer megsemmisíti őket.

Az aszinkron megközelítés megakadályozza a hívó szál blokkolását, amíg az I/O függőben van. Az átviteli sebesség javítása sok esetben az operációs rendszertől, a hardvertől és néhány platformtól függ, például a .NET-futtatókörnyezet viselkedésétől, például a szálkészlet korlátaitól és az ütemezéstől.

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

A WriteAsync és a ReadAsync metódusok használatakor megadhat egy CancellationToken-t, hogy a műveletet megszakítsa a folyamat közben. További információ: Lemondás felügyelt szálakban.

Lásd még