Akses file asinkron (C#)

Dengan menggunakan fitur asinkron untuk mengakses file, Anda dapat memanggil metode asinkron tanpa menggunakan panggilan balik atau memisahkan kode Anda di beberapa metode atau ekspresi lambda. Untuk membuat kode sinkron menjadi asinkron, panggil metode asinkron daripada menggunakan metode sinkron dan tambahkan beberapa kata kunci pada kode.

Pertimbangkan untuk menambahkan asinkron ke panggilan akses file karena alasan berikut:

  • Asinkron membuat aplikasi UI lebih responsif karena utas UI yang meluncurkan operasi dapat melakukan pekerjaan lain. Jika utas UI harus menjalankan kode yang membutuhkan waktu lama (misalnya, lebih dari 50 milidetik), UI mungkin membeku hingga I/O selesai dan utas UI dapat kembali memproses input keyboard dan mouse dan peristiwa lainnya.
  • Asinkron meningkatkan skalabilitas ASP.NET dan aplikasi berbasis server lainnya dengan mengurangi kebutuhan akan thread. Jika aplikasi menggunakan utas khusus per respons dan seribu permintaan ditangani secara bersamaan, seribu utas diperlukan. Operasi asinkron sering kali tidak perlu menggunakan utas selama penantian. Mereka menggunakan utas penyelesaian I/O yang ada secara singkat di akhir.
  • Latensi operasi akses file mungkin sangat rendah dalam kondisi saat ini, tetapi latensinya mungkin sangat meningkat di masa depan. Misalnya, file mungkin dipindahkan ke server yang ada di seluruh dunia.
  • Overhead tambahan dari penggunaan fitur Asinkron kecil.
  • Beberapa operasi I/O asinkron dapat berjalan tanpa memblokir utas panggilan.

Gunakan kelas yang sesuai

Contoh sederhana dalam topik ini menunjukkan File.WriteAllTextAsync dan File.ReadAllTextAsync. Untuk kontrol yang baik atas operasi I/O file, gunakan FileStream kelas , yang memiliki opsi yang menyebabkan I/O asinkron terjadi pada tingkat sistem operasi. Dengan menggunakan opsi ini, Anda dapat menghindari pemblokiran utasan thread pool dalam banyak kasus. Untuk mengaktifkan opsi ini, tentukan argumen useAsync=true atau options=FileOptions.Asynchronous dalam panggilan konstruktor.

Anda tidak dapat menggunakan opsi ini dengan StreamReader dan StreamWriter jika Anda membukanya secara langsung dengan menentukan jalur file. Namun, Anda dapat menggunakan opsi ini jika Anda memberikan kepada mereka Stream yang dibuka oleh kelas FileStream. Panggilan asinkron lebih cepat dalam aplikasi antarmuka pengguna (UI) bahkan ketika utas dari kumpulan utas diblokir, karena utas UI tidak diblokir selama menunggu.

Tulis teks

Contoh berikut menulis teks ke file. Pada setiap pernyataan tunggu, metode segera keluar. Ketika I/O file selesai, fungsi dilanjutkan pada pernyataan yang mengikuti perintah await. Pengubah asinkron berada dalam definisi metode yang menggunakan pernyataan tunggu.

Contoh sederhana

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

    await File.WriteAllTextAsync(filePath, text);
}

Contoh pengendalian terbatas

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

Contoh asli memiliki pernyataan await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);, yang merupakan kontraksi dari dua pernyataan berikut:

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

Pernyataan pertama mengembalikan tugas dan menyebabkan pemrosesan file dimulai. Pernyataan kedua dengan menunggu menyebabkan metode segera keluar dan mengembalikan tugas yang berbeda. Ketika pemrosesan file selesai nanti, eksekusi dilanjutkan ke pernyataan yang berada setelah await.

Membaca teks

Contoh berikut membaca teks dari file.

Contoh sederhana

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

    Console.WriteLine(text);
}

Contoh kendali terbatas

Teks di-buffer dan, dalam hal ini, ditempatkan ke dalam StringBuilder. Tidak seperti dalam contoh sebelumnya, evaluasi menunggu menghasilkan nilai. Metode ReadAsync mengembalikan Task<Int32>, sehingga evaluasi await menghasilkan nilai Int32numRead setelah operasi selesai. Untuk informasi selengkapnya, lihat Jenis Pengembalian Asinkron (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();
}

Beberapa operasi I/O asinkron

Contoh berikut memulai beberapa operasi penulisan asinkron. Runtime mengantrekan operasi ini, dan implementasi yang mendasarinya mungkin menggunakan I/O asinkron sistem operasi (OS) atau utas kumpulan utas tergantung pada platform dan konfigurasi, sehingga konkurensi aktual tergantung pada OS dan perangkat keras.

Contoh sederhana

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

Contoh pengendalian terbatas

Untuk setiap file, metode WriteAsync mengembalikan tugas yang ditambahkan ke daftar tugas. Pernyataan await Task.WhenAll(tasks); keluar dari metode dan melanjutkan dalam metode ketika pemrosesan file selesai untuk semua tugas.

Contoh menutup semua instance FileStream dalam blok finally setelah tugas selesai. Jika masing-masing FileStream dinyatakan di dalam pernyataan using, FileStream mungkin dibuang sebelum tugas selesai.

Pendekatan asinkron menghindari pemblokiran utas panggilan saat I/O tertunda. Dalam banyak kasus, peningkatan throughput tergantung pada OS, perangkat keras, dan, pada beberapa platform, perilaku runtime .NET seperti batas kumpulan utas dan penjadwalan.

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

Saat menggunakan metode WriteAsync dan ReadAsync, Anda dapat menentukan CancellationToken untuk membatalkan operasi di tengah proses. Untuk informasi lebih lanjut, lihat Pembatalan pada thread yang dikelola.

Baca juga