Entity Framework SaveChanges () ile SaveChangesAsync () ve Find () ile FindAsync () karşılaştırması


89

Yukarıdaki 2 çift arasındaki farkları araştırıyordum, ancak ne zaman kullanılacağının yanı sıra bunu açıkça açıklayan herhangi bir makale bulamadım.

Peki SaveChanges()ve arasındaki fark SaveChangesAsync()nedir?
Ve arasında Find()ve FindAsync()?

Sunucu tarafında Asyncmetotları kullandığımızda ayrıca eklememiz gerekiyor await. Bu yüzden sunucu tarafında asenkron olduğunu düşünmüyorum.

Yalnızca istemci tarafı tarayıcıda UI engellemesini önlemeye yardımcı olur mu? Veya aralarında herhangi bir artı ve eks var mı?


2
zaman uyumsuz, istemci kullanıcı arabirimi iş parçacığının istemci uygulamalarında engellenmesini durdurmaktan çok daha fazlasıdır. Eminim kısa süre sonra bir uzman cevabı verilecektir.
jdphenix

Yanıtlar:


176

Uzak bir sunucuda herhangi bir işlem yapmanız gerektiğinde, programınız isteği oluşturur, gönderir ve ardından bir yanıt bekler. Örnek olarak SaveChanges()ve kullanacağım SaveChangesAsync()ama aynısı Find()ve için de geçerli FindAsync().

myListVeritabanınıza eklemeniz gereken 100'den fazla öğe listeniz olduğunu varsayalım. Bunu eklemek için, işleviniz şuna benzer:

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

Önce bir örneğini oluşturursunuz MyEDM, listeyi myListtabloya eklersiniz MyTable, ardından SaveChanges()değişiklikleri veritabanında sürdürmek için çağırırsınız . İstediğiniz gibi çalışır, kayıtlar işlenir, ancak programınız kesinleştirme bitene kadar başka bir şey yapamaz. Bu, ne taahhüt ettiğinize bağlı olarak uzun zaman alabilir. Kayıtlarda değişiklik yapıyorsanız, kuruluş bunları teker teker işlemelidir (bir kez kaydetmiştim, güncellemeler için 2 dakika ayırın)!

Bu sorunu çözmek için iki şeyden birini yapabilirsiniz. İlki, eki işlemek için yeni bir diş açabilirsiniz. Bu, çalıştırmaya devam etmek için çağıran iş parçacığını serbest bırakacak olsa da, orada oturup bekleyecek yeni bir iş parçacığı oluşturdunuz. Bu ek yüke gerek yoktur ve async awaitmodelin çözdüğü şey budur .

I / O işlemleri için, awaitçabucak en iyi arkadaşınız olur. Kod bölümünü yukarıdan alarak, şu şekilde değiştirebiliriz:

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

Bu çok küçük bir değişikliktir, ancak kodunuzun verimliliği ve performansı üzerinde derin etkileri vardır. Peki ne olur? Kodun başlangıcı size bir örneğini oluşturmak, aynı MyEDMve eklemek myListTo MyTable. Ancak aradığınızda await context.SaveChangesAsync(), kodun yürütülmesi çağıran işleve geri döner! Böylece, tüm bu kayıtların işlenmesini beklerken, kodunuz çalışmaya devam edebilir. Yukarıdaki kodu içeren işlevin imzasına sahip olduğunu public async Task SaveRecords(List<MyTable> saveList)varsayalım, çağıran işlev şöyle görünebilir:

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

Neden böyle bir işleve sahip olurdunuz bilmiyorum ama çıkardığı şey nasıl async awaitçalıştığını gösteriyor . İlk önce ne olduğunu gözden geçirelim.

Yürütme girer MyCallingFunction, Function Startingardından Save Startingkonsola yazılır, ardından işlev SaveChangesAsync()çağrılır. Bu noktada, yürütme MyCallingFunction1000 defaya kadar 'Yürütmeye Devam Ediyor' yazan for döngüsüne döner ve girer. Bittiğinde SaveChangesAsync(), yürütme konsola SaveRecordsyazarak işleve döner Save Complete. Her şey SaveRecordstamamlandığında, yürütme tamamlandığında MyCallingFunctionolduğu gibi devam edecek SaveChangesAsync(). Şaşkın? İşte bir örnek çıktı:

İşlev Başlangıcı
Başlangıcı Kaydet
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
....
Yürütmeye devam ediyor!
Kaydetme Tamamlandı!
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
....
Yürütmeye devam ediyor!
İşlev Tamamlandı!

Ya da belki:

İşlev Başlangıcı
Başlangıcı Kaydet
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
Kaydetme Tamamlandı!
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
Yürütmeye devam ediyor!
....
Yürütmeye devam ediyor!
İşlev Tamamlandı!

İşin güzelliği bu async await, bir şeyin bitmesini beklerken kodunuz çalışmaya devam edebilir. Gerçekte, arama işleviniz olarak şuna benzer bir işleve sahip olursunuz:

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

Burada, aynı anda giden dört farklı kaydetme kayıt fonksiyonuna sahipsiniz . işlevlerin seri olarak çağrılmasına göre MyCallingFunctionçok daha hızlı tamamlanır .async awaitSaveRecords

Henüz değinmediğim tek şey awaitanahtar kelime. Bunun yaptığı şey Task, beklediğiniz şey tamamlanana kadar mevcut işlevin yürütülmesini durdurmaktır . Yani orijinal durumunda , fonksiyon bitene kadar MyCallingFunctionsatır Function Completekonsola yazılmayacaktır SaveRecords.

Uzun lafın kısası, kullanma seçeneğiniz varsa async await, uygulamanızın performansını büyük ölçüde artıracağı için yapmalısınız.


7
Devam etmeden önce, zamanın% 99'unda değerlerin veritabanından alınmasını beklemem gerekiyor. Yine de eşzamansız kullanmalı mıyım? Eşzamansız, 100 kişinin web siteme eşzamansız olarak bağlanmasına izin verir mi? Zaman uyumsuz kullanmazsam, bu 100 kullanıcının her birinin aynı anda 1. satırda beklemesi gerektiği anlamına mı gelir?
MIKE

6
Dikkate değer: İş parçacığı havuzundan yeni bir iş parçacığı oluşturmak, ASP'den bir iş parçacığını soyduğunuz için ASP'yi üzgün bir panda yapar (bu, iş parçacığının diğer istekleri işleyemeyeceği veya bir engelleme çağrısında kaldığı için hiçbir şey yapamayacağı anlamına gelir). awaitAncak kullanırsanız , SaveChanges çağrısından sonra başka bir şey yapmanız gerekmese bile, ASP "aha, bu iş parçacığı zaman uyumsuz bir işlemi beklerken geri döndü, bu da bu iş parçacığının bu arada başka istekleri işlemesine izin verebileceğim anlamına gelir. ! " Bu, uygulamanızın yatay olarak çok daha iyi ölçeklenmesini sağlar.
sara

3
Aslında asenkronu daha yavaş olarak değerlendirdim. Ve tipik bir ASP.Net sunucusunda kaç tane iş parçacığı olduğunu gördünüz mü? Onbinlerce gibi. Dolayısıyla, daha fazla istekle başa çıkmak için iş parçacığının tükenme olasılığı çok düşüktür ve tüm bu iş parçacıklarını doyurmak için yeterli trafiğiniz olsa bile, sunucunuz bu durumda yine de takılmayacak kadar güçlü mü? Async'i her yerde kullanmanın performansı artırdığını iddia etmek tamamen yanlıştır. Bazı senaryolarda olabilir, ancak çoğu durumda aslında daha yavaş olacaktır. Kıyaslayın ve görün.
user3766657

@MIKE tek bir kullanıcının veri tabanının devam etmesi için veri döndürmesini beklemesi gerekirken, uygulamanızı kullanan diğer kullanıcılarınız beklemiyor. IIS her istek için bir iş parçacığı oluştursa da (aslında bundan daha karmaşıktır), bekleyen iş parçacığınız diğer istekleri işlemek için kullanılabilir, bu durum ölçeklenebilirlik açısından önemlidir. Her isteği görüntüleyerek, 1 iş parçacığını tam zamanlı kullanmak yerine, başka bir yerde yeniden kullanılabilen çok daha kısa iş parçacığı kullanır (diğer bir deyişle başka istekler).
Bart Calixto

1
EF, aynı anda birden fazla kaydetmeyi desteklemediğinden , her zaman await kullanmanız gerektiğini eklemek istiyorum SaveChangesAsync. docs.microsoft.com/en-us/ef/core/saving/async Ayrıca, bu eşzamansız yöntemleri kullanmanın aslında büyük bir avantajı vardır. Örneğin, verileri kaydederken veya çok fazla kesinti yaparken webApi'nizde başka istekler almaya devam edebilir veya bir masaüstü uygulamasındayken arayüzü dondurmadan kullanıcı deneyimini iyileştirebilirsiniz.
tgarcia

1

Kalan açıklamam aşağıdaki kod parçacığını temel alacaktır.

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

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

görüntü açıklamasını buraya girin

Açıklamalar: Senkronize kısım (yeşil) JobAsyncgörevden t(kırmızı) daha uzun döndüğünden, görev tnoktasında zaten tamamlanmıştır await t. Sonuç olarak, devam (mavi) yeşil olanla aynı iş parçacığı üzerinde çalışır. MainYeşil olanın dönüşü bittikten sonra (beyaz) ' ın eşzamanlı kısmı dönecektir. Bu nedenle asenkron yöntemdeki senkron kısım sorunludur.

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

görüntü açıklamasını buraya girin

Açıklamalar: Bu durum ilk durumun tersidir. Senkronize kısım (yeşil) JobAsyncgörevden daha kısa dönüyor t(kırmızı), ardından görev tnoktasında tamamlanmadı await t. Sonuç olarak, devam (mavi) yeşil olanla farklı bir iş parçacığı üzerinde çalışır. MainYeşil olanın dönüşü bittikten sonra (beyaz) ' ın eşzamanlı kısmı hala dönüyor.

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

görüntü açıklamasını buraya girin

Açıklamalar: Bu durum, asenkron yöntemde senkron parça ile ilgili önceki durumlarda sorunu çözecektir. Görev themen bekleniyor. Sonuç olarak, devam (mavi) yeşil olanla farklı bir iş parçacığı üzerinde çalışır. Main(Beyaz) ' ın eşzamanlı kısmı hemen paralel olarak dönecektir JobAsync.

Başka vakalar eklemek istiyorsanız, düzenlemekten çekinmeyin.


0

Bu ifade yanlıştır:

Sunucu tarafında Async yöntemlerini kullandığımızda ayrıca await eklememiz gerekir.

"Await" eklemenize gerek yoktur await, C # 'da çağrıdan sonra daha fazla kod satırı yazmanıza olanak veren uygun bir anahtar sözcüktür ve bu diğer satırlar yalnızca Kaydetme işlemi tamamlandıktan sonra çalıştırılır. Ama belirttiğiniz gibi, bunu sadece arayarak SaveChangesyerine getirebilirsiniz SaveChangesAsync.

Ancak temelde, eşzamansız bir çağrı bundan çok daha fazlasıdır. Buradaki fikir, Kaydetme işlemi devam ederken (sunucuda) yapabileceğiniz başka işler varsa, kullanmanız gerektiğidir SaveChangesAsync. "Await" kullanmayın. Sadece arayın SaveChangesAsyncve paralel olarak başka şeyler yapmaya devam edin. Bu, potansiyel olarak bir web uygulamasında, Kaydetme tamamlanmadan önce istemciye bir yanıt döndürmeyi içerir. Ama tabii ki, yine de Kaydet'in nihai sonucunu kontrol etmek isteyeceksiniz, böylece başarısız olursa, bunu kullanıcınıza iletebilir veya bir şekilde kaydedebilirsiniz.


5
Gerçekte bu çağrıları beklemek istiyorsunuz, aksi takdirde aynı DbContext örneğini kullanarak sorgu çalıştırabilir veya verileri aynı anda kaydedebilirsiniz ve DbContext iş parçacığı güvenli değildir. Üstelik bekleme, istisnaların üstesinden gelmeyi kolaylaştırır. Beklemeden görevi saklamanız ve hatalı olup olmadığını kontrol etmeniz gerekir, ancak görevin ne zaman tamamlandığını bilmeden, beklemekten çok daha fazla düşünmeyi gerektiren '.ContinueWith' kullanmadığınız sürece ne zaman kontrol edeceğinizi bilemezsiniz.
Pawel

24
Bu yanıt aldatıcıdır. Beklemeden zaman uyumsuz bir yöntemi çağırmak, onu "ateş et ve unut" yapar. Yöntem kapanır ve muhtemelen bir ara tamamlanır, ancak ne zaman olacağını asla bilemezsiniz ve bir istisna atarsa, onu asla duymazsınız, Tamamlanmasıyla senkronize edemezsiniz. Bu tür potansiyel olarak tehlikeli davranışlar seçilmeli, "istemcide bekliyor, sunucuda bekleme yok" gibi basit (ve yanlış) bir kuralla çalıştırılmamalıdır.
John Melville

1
Bu, belgelerde okuduğum, ancak gerçekten düşünmediğim çok yararlı bir bilgi parçası. Yani, şu seçeneğiniz var: 1. John Melville'in dediği gibi, SaveChangesAsync () "Ateş et ve unut" a ... ki bu bazı durumlarda benim için yararlı. Daha sonra Ateş, arayana dönüşü ve" 2. bekliyoruz SaveChangesAsync () bazı 'yazı-save' yürütmek kod tamamlandıktan tasarrufu sonra Çok yararlı bir parça teşekkür ederiz...
Parrhesia Joe
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.