Entity Framework SaveChanges () vs. SaveChangesAsync () dan Find () vs. FindAsync ()

89

Saya telah mencari perbedaan antara 2 pasang di atas tetapi belum menemukan artikel yang menjelaskan dengan jelas tentang hal itu serta kapan harus menggunakan satu atau lainnya.

Jadi apa perbedaan antara SaveChanges()dan SaveChangesAsync()?
Dan antara Find()dan FindAsync()?

Di sisi server, saat kami menggunakan Asyncmetode, kami juga perlu menambahkan await. Jadi, menurut saya ini tidak asinkron di sisi server.

Apakah ini hanya membantu mencegah pemblokiran UI di browser sisi klien? Ataukah ada pro dan kontra di antara keduanya?

Hien Tran
sumber
2
async jauh lebih dari sekadar menghentikan utas UI klien agar tidak memblokir dalam aplikasi klien. Saya yakin ada jawaban ahli segera.
jdphenix

Jawaban:

176

Kapan pun Anda perlu melakukan tindakan di server jarak jauh, program Anda membuat permintaan, mengirimkannya, lalu menunggu tanggapan. Saya akan menggunakan SaveChanges()dan SaveChangesAsync()sebagai contoh tetapi hal yang sama berlaku untuk Find()dan FindAsync().

Katakanlah Anda memiliki daftar myList100+ item yang perlu Anda tambahkan ke database Anda. Untuk memasukkannya, fungsi Anda akan terlihat seperti ini:

using(var context = new MyEDM())
{
    context.MyTable.AddRange(myList);
    context.SaveChanges();
}

Pertama Anda membuat instance MyEDM, menambahkan daftar myListke tabel MyTable, lalu memanggil SaveChanges()untuk mempertahankan perubahan ke database. Ini berfungsi seperti yang Anda inginkan, record dikomit, tetapi program Anda tidak dapat melakukan apa pun sampai komit selesai. Ini bisa memakan waktu lama tergantung pada apa yang Anda lakukan. Jika Anda melakukan perubahan pada record, entitas harus mengkomitnya satu per satu (saya pernah menyimpan membutuhkan waktu 2 menit untuk update)!

Untuk mengatasi masalah ini, Anda dapat melakukan salah satu dari dua hal. Yang pertama adalah Anda dapat memulai utas baru untuk menangani penyisipan. Meskipun ini akan membebaskan utas panggilan untuk terus mengeksekusi, Anda membuat utas baru yang hanya akan duduk di sana dan menunggu. Tidak perlu biaya tambahan itu, dan inilah yang async awaitdipecahkan oleh polanya.

Untuk pengoperasian I / O, awaitcepatlah menjadi sahabat Anda. Mengambil bagian kode dari atas, kita dapat memodifikasinya menjadi:

using(var context = new MyEDM())
{
    Console.WriteLine("Save Starting");
    context.MyTable.AddRange(myList);
    await context.SaveChangesAsync();
    Console.WriteLine("Save Complete");
}

Ini adalah perubahan yang sangat kecil, tetapi ada efek yang sangat besar pada efisiensi dan kinerja kode Anda. Lalu apa yang terjadi? Awal kodenya sama, Anda membuat instance MyEDMdan menambahkan myListke MyTable. Tetapi ketika Anda memanggil await context.SaveChangesAsync(), eksekusi kode kembali ke fungsi panggilan! Jadi, saat Anda menunggu semua record tersebut dijalankan, kode Anda dapat terus dieksekusi. Katakanlah fungsi yang berisi kode di atas memiliki tanda tangan public async Task SaveRecords(List<MyTable> saveList), fungsi pemanggilan bisa terlihat seperti ini:

public async Task MyCallingFunction()
{
    Console.WriteLine("Function Starting");
    Task saveTask = SaveRecords(GenerateNewRecords());

    for(int i = 0; i < 1000; i++){
        Console.WriteLine("Continuing to execute!");
    }

    await saveTask;
    Console.Log("Function Complete");
}

Mengapa Anda memiliki fungsi seperti ini, saya tidak tahu, tetapi apa yang dihasilkannya menunjukkan cara async awaitkerjanya. Pertama, mari kita bahas apa yang terjadi.

Eksekusi masuk MyCallingFunction, Function Startinglalu Save Startingditulis ke konsol, lalu fungsi SaveChangesAsync()dipanggil. Pada titik ini, eksekusi kembali ke MyCallingFunctiondan memasuki penulisan loop for 'Continuing to Execute' hingga 1000 kali. Setelah SaveChangesAsync()selesai, eksekusi kembali ke SaveRecordsfungsi, menulis Save Completeke konsol. Setelah semuanya SaveRecordsselesai, eksekusi akan dilanjutkan MyCallingFunctionjika sudah SaveChangesAsync()selesai. Bingung? Berikut adalah contoh keluarannya:

Fungsi Mulai
Simpan Mulai
Terus mengeksekusi!
Terus mengeksekusi!
Terus mengeksekusi!
Terus mengeksekusi!
Terus mengeksekusi!
....
Terus mengeksekusi!
Simpan Selesai!
Terus mengeksekusi!
Terus mengeksekusi!
Terus mengeksekusi!
....
Terus mengeksekusi!
Fungsi Selesai!

Atau mungkin:

Fungsi Mulai
Simpan Mulai
Terus mengeksekusi!
Terus mengeksekusi!
Simpan Selesai!
Terus mengeksekusi!
Terus mengeksekusi!
Terus mengeksekusi!
....
Terus mengeksekusi!
Fungsi Selesai!

Itulah keindahannya async await, kode Anda dapat terus berjalan saat Anda menunggu sesuatu untuk diselesaikan. Pada kenyataannya, Anda akan memiliki fungsi yang lebih seperti ini sebagai fungsi panggilan Anda:

public async Task MyCallingFunction()
{
    List<Task> myTasks = new List<Task>();
    myTasks.Add(SaveRecords(GenerateNewRecords()));
    myTasks.Add(SaveRecords2(GenerateNewRecords2()));
    myTasks.Add(SaveRecords3(GenerateNewRecords3()));
    myTasks.Add(SaveRecords4(GenerateNewRecords4()));

    await Task.WhenAll(myTasks.ToArray());
}

Di sini, Anda memiliki empat fungsi penyimpanan catatan yang berbeda yang berjalan pada saat bersamaan . MyCallingFunctionakan menyelesaikan penggunaan lebih cepat async awaitdaripada jika SaveRecordsfungsi individu dipanggil secara seri.

Satu hal yang belum saya singgung adalah awaitkata kunci. Apa yang dilakukannya adalah menghentikan fungsi saat ini dari eksekusi hingga apa pun yang TaskAnda tunggu selesai. Jadi dalam kasus aslinya MyCallingFunction, baris Function Completetidak akan ditulis ke konsol sampai SaveRecordsfungsinya selesai.

Singkat cerita, jika Anda memiliki opsi untuk menggunakan async await, Anda harus melakukannya karena akan sangat meningkatkan kinerja aplikasi Anda.

Jacob Lambert
sumber
7
99% dari waktu saya masih harus menunggu nilai diterima dari database sebelum saya dapat melanjutkan. Haruskah saya tetap menggunakan asinkron? Apakah async memungkinkan 100 orang untuk terhubung ke situs web saya secara asinkron? Jika saya tidak menggunakan async, apakah itu berarti semua 100 pengguna harus menunggu di baris 1 sekaligus?
MIKE
6
Perlu diperhatikan: Memunculkan utas baru dari kumpulan utas membuat ASP panda yang menyedihkan karena pada dasarnya Anda merampok utas dari ASP (artinya utas tidak dapat menangani permintaan lain atau melakukan apa pun karena terjebak dalam panggilan pemblokiran). Namun jika Anda menggunakan await, meskipun ANDA tidak perlu melakukan apa pun setelah panggilan ke SaveChanges, ASP akan mengatakan "aha, utas ini kembali menunggu operasi asinkron, ini berarti saya dapat membiarkan utas ini menangani beberapa permintaan lain untuk sementara waktu ! " Ini membuat skala aplikasi Anda jauh lebih baik secara horizontal.
sara
3
Sebenarnya saya telah membandingkan async menjadi lebih lambat. Dan apakah Anda pernah melihat berapa banyak utas yang tersedia di server ASP.Net biasa? Ini seperti puluhan ribu. Jadi kemungkinan kehabisan utas untuk menangani permintaan lebih lanjut sangat tidak mungkin dan bahkan jika Anda memang memiliki lalu lintas yang cukup untuk memenuhi semua utas tersebut, apakah server Anda benar-benar cukup kuat untuk tidak menyerah dalam kasus itu? Untuk membuat klaim bahwa menggunakan async di mana-mana meningkatkan kinerja adalah salah total. Ini bisa dalam skenario tertentu, tetapi dalam kebanyakan situasi umum itu sebenarnya akan lebih lambat. Tolok ukur dan lihat.
pengguna3766657
@MIKE sementara satu pengguna harus menunggu database mengembalikan data untuk melanjutkan, pengguna lain yang menggunakan aplikasi Anda tidak. Sementara IIS membuat utas untuk setiap permintaan (sebenarnya lebih kompleks dari itu), utas menunggu Anda dapat digunakan untuk menangani permintaan lain, ini penting untuk skalabilitas afaik. Mencitrakan setiap permintaan, alih-alih menggunakan 1 utas secara penuh waktu, ini menggunakan banyak utas yang lebih pendek yang dapat digunakan kembali di tempat lain (alias permintaan lain).
Bart Calixto
1
Saya hanya ingin menambahkan bahwa Anda harus selalu await melakukannya SaveChangesAsynckarena EF tidak mendukung banyak penyelamatan pada saat yang bersamaan. docs.microsoft.com/en-us/ef/core/saving/async Selain itu, sebenarnya ada keuntungan besar dalam menggunakan metode asinkron ini. Misalnya, Anda dapat terus menerima permintaan lain di webApi Anda saat menyimpan data atau melakukan banyak penanganan, atau meningkatkan pengalaman pengguna dengan tidak membekukan antarmuka saat Anda berada di aplikasi desktop.
tgarcia
1

Penjelasan saya yang tersisa akan didasarkan pada potongan kode berikut.

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

public static class Program
{
    const int N = 20;
    static readonly object obj = new object();
    static int counter;

    public static void Job(ConsoleColor color, int multiplier = 1)
    {
        for (long i = 0; i < N * multiplier; i++)
        {
            lock (obj)
            {
                counter++;
                ForegroundColor = color;
                Write($"{Thread.CurrentThread.ManagedThreadId}");
                if (counter % N == 0) WriteLine();
                ResetColor();
            }
            Thread.Sleep(N);
        }
    }

    static async Task JobAsync()
    {
       // intentionally removed
    }

    public static async Task Main()
    {
       // intentionally removed
    }
}

Kasus 1

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 1));
    Job(ConsoleColor.Green, 2);
    await t;
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

masukkan deskripsi gambar di sini

Keterangan: Karena bagian sinkron (hijau) JobAsyncberputar lebih panjang dari tugas t(merah) maka tugas tsudah selesai pada titik await t. Akibatnya, lanjutan (biru) berjalan pada utas yang sama dengan utas hijau. Bagian sinkron Main(putih) akan berputar setelah bagian hijau selesai berputar. Itulah mengapa bagian sinkron dalam metode asinkron bermasalah.

Kasus 2

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 2));
    Job(ConsoleColor.Green, 1);
    await t;
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

masukkan deskripsi gambar di sini

Keterangan: Kasus ini berlawanan dengan kasus pertama. Bagian sinkron (hijau) JobAsyncberputar lebih pendek dari tugas t(merah) maka tugas tbelum selesai pada titik await t. Akibatnya, lanjutan (biru) berjalan pada utas yang berbeda dengan utas hijau. Bagian sinkron Main(putih) masih berputar setelah bagian hijau selesai berputar.

Kasus 3

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 1));
    await t;
    Job(ConsoleColor.Green, 1);
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

masukkan deskripsi gambar di sini

Catatan: Kasus ini akan menyelesaikan masalah dalam kasus sebelumnya tentang bagian sinkron dalam metode asinkron. Tugasnya tlangsung ditunggu. Akibatnya, lanjutan (biru) berjalan pada utas yang berbeda dengan utas hijau. Bagian sinkron Main(putih) akan langsung berputar sejajar JobAsync.

Jika Anda ingin menambahkan kasus lain, silakan edit.

Kebodohan Buatan
sumber
0

Pernyataan ini salah:

Di sisi server, saat kita menggunakan metode Async, kita juga perlu menambahkan await.

Anda tidak perlu menambahkan "await", awaitini hanyalah kata kunci yang nyaman di C # yang memungkinkan Anda untuk menulis lebih banyak baris kode setelah panggilan, dan baris lainnya hanya akan dijalankan setelah operasi Simpan selesai. Tetapi seperti yang Anda tunjukkan, Anda dapat melakukannya hanya dengan menelepon, SaveChangesbukan SaveChangesAsync.

Namun pada dasarnya, panggilan asinkron lebih dari itu. Idenya di sini adalah bahwa jika ada pekerjaan lain yang dapat Anda lakukan (di server) saat operasi Simpan sedang berlangsung, maka Anda harus menggunakan SaveChangesAsync. Jangan gunakan "menunggu". Panggil saja SaveChangesAsync, lalu lanjutkan melakukan hal lain secara paralel. Ini termasuk kemungkinan, dalam aplikasi web, mengembalikan respons ke klien bahkan sebelum Penyimpanan selesai. Tetapi tentu saja, Anda masih ingin memeriksa hasil akhir dari Simpan sehingga jika gagal, Anda dapat mengkomunikasikannya kepada pengguna Anda, atau mencatatnya entah bagaimana.

Rajeev Goel
sumber
5
Anda sebenarnya ingin menunggu panggilan ini jika tidak, Anda mungkin menjalankan kueri dan atau menyimpan data secara bersamaan menggunakan instance DbContext yang sama dan DbContext tidak aman untuk thread. Di atas semua itu menunggu membuatnya mudah untuk menangani pengecualian. Tanpa menunggu Anda harus menyimpan tugas dan memeriksa apakah itu salah tetapi tanpa mengetahui kapan tugas selesai Anda tidak akan tahu kapan harus memeriksa kecuali Anda menggunakan '.ContinueWith' yang membutuhkan lebih banyak pemikiran daripada menunggu.
Pawel
24
Jawaban ini menipu, Memanggil metode asinkron tanpa menunggu menjadikannya "api dan lupakan." Metode ini mati dan mungkin akan selesai suatu saat, tetapi Anda tidak akan pernah tahu kapan, dan jika metode ini mengeluarkan pengecualian, Anda tidak akan pernah mendengarnya, Anda tidak dapat menyinkronkan dengan penyelesaiannya. Jenis perilaku yang berpotensi berbahaya ini harus dipilih, tidak dijalankan dengan aturan sederhana (dan salah) seperti "menunggu di klien, tidak menunggu di server".
John Melville
1
Ini adalah bagian pengetahuan yang sangat berguna yang pernah saya baca dalam dokumentasi, tetapi belum benar-benar dipertimbangkan. Jadi, Anda memiliki opsi untuk: 1. SaveChangesAsync () ke "Aktifkan dan lupakan", seperti yang dikatakan John Melville ... yang berguna bagi saya dalam beberapa kasus. 2. menunggu SaveChangesAsync () ke "Aktifkan, kembali ke pemanggil, dan kemudian jalankan beberapa kode 'pasca-simpan' setelah penyimpanan selesai. Bagian yang sangat berguna. Terima kasih.
Parrhesia Joe