Async / await'i ASP.NET Web API ile etkili bir şekilde kullanın


114

async/awaitWeb API projemde ASP.NET özelliğini kullanmaya çalışıyorum . Web API hizmetimin performansında herhangi bir fark yaratıp yaratmayacağından pek emin değilim. Lütfen iş akışını ve uygulamamdan örnek kodu bulun.

İş Akışı:

UI Uygulaması → Web API uç noktası (denetleyici) → Web API hizmet katmanında çağrı yöntemi → Başka bir harici web hizmetini çağırın. (Burada DB etkileşimlerimiz vb. Var.)

Denetleyici:

public async Task<IHttpActionResult> GetCountries()
{
    var allCountrys = await CountryDataService.ReturnAllCountries();

    if (allCountrys.Success)
    {
        return Ok(allCountrys.Domain);
    }

    return InternalServerError();
}

Hizmet Katmanı:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");

    return Task.FromResult(response);
}

Yukarıdaki kodu test ettim ve çalışıyor. Ama doğru kullanım olup olmadığından emin değilim async/await. Lütfen düşüncelerinizi paylaşın.


Yanıtlar:


203

API performansımda herhangi bir fark yaratıp yaratmayacağından pek emin değilim.

Eşzamansız kodun sunucu tarafında birincil faydasının ölçeklenebilirlik olduğunu unutmayın . Sihirli bir şekilde isteklerinizi daha hızlı çalıştırmaz. ASP.NET hakkındaki makalemde birkaç "kullanmalı mıyım async" konusunu ele alıyorum .async

Kullanım durumunuzun (diğer API'leri çağırmak) eşzamansız kod için çok uygun olduğunu düşünüyorum, "eşzamansız" ifadesinin "daha hızlı" anlamına gelmediğini unutmayın. En iyi yaklaşım, önce kullanıcı arayüzünüzü duyarlı ve eşzamansız hale getirmektir ; bu, biraz daha yavaş olsa bile uygulamanızı daha hızlı hissettirir .

Kodun gittiği kadarıyla, bu eşzamansız değildir:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
  var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
  return Task.FromResult(response);
}

Aşağıdakilerin ölçeklenebilirlik avantajlarından yararlanmak için gerçekten eşzamansız bir uygulamaya ihtiyacınız vardır async:

public async Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()
{
  return await _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}

Veya (bu yöntemdeki mantığınız gerçekten sadece bir geçiş ise):

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()
{
  return _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}

Bunun gibi "dışarıdan içeriye" değil, "içten dışa" çalışmanın daha kolay olduğunu unutmayın. Başka bir deyişle, zaman uyumsuz bir denetleyici eylemiyle başlamayın ve ardından aşağı akış yöntemlerini eşzamansız olmaya zorlamayın. Bunun yerine, doğal olarak eşzamansız işlemleri (harici API'leri çağırma, veritabanı sorguları vb.) Tanımlayın ve bunları önce en düşük düzeyde eşzamansız hale getirin ( Service.ProcessAsync). Ardından async, son adım olarak denetleyici eylemlerinizi eşzamansız hale getirerek , damlama yapmasına izin verin .

Ve hiçbir koşulda Task.Runbu senaryoda kullanmamalısınız .


4
Stephen değerli yorumlarınız için teşekkürler. Tüm hizmet katmanı yöntemlerimi zaman uyumsuz olacak şekilde değiştirdim ve şimdi ExecuteTaskAsync yöntemini kullanarak harici REST çağrımı çağırıyorum ve beklendiği gibi çalışıyor. Zaman uyumsuz ve Görevler ile ilgili blog yazılarınız için de teşekkür ederiz. İlk anlayışa sahip olmama gerçekten çok yardımcı oldu.
arp

5
Task.Run'ı neden kullanmamanız gerektiğini açıklayabilir misiniz?
Maarten

1
@Maarten: Bağlantı verdiğim makalede (kısaca) açıklıyorum . Blogumda daha fazla ayrıntıya giriyorum .
Stephen Cleary

Stephen makaleni okudum ve görünüşe göre bir şeyden bahsetmekten kaçınıyor. Bir ASP.NET isteği geldiğinde ve başladığında, bir iş parçacığı havuzu iş parçacığında çalışıyor, sorun değil. Ancak, zaman uyumsuz hale gelirse, evet, zaman uyumsuz işlemi başlatır ve bu iş parçacığı hemen havuza geri döner. Ancak zaman uyumsuz yöntemde gerçekleştirilen iş, bir iş parçacığı havuzu iş parçacığında yürütülecektir!
Hugh


12

Doğru, ama belki de kullanışlı değil.

Beklenecek hiçbir şey olmadığından - eşzamansız olarak çalışabilen engelleyici API'lere çağrı yapılmadığından - o zaman asenkron işlemi (ek yükü olan) izlemek için yapılar oluşturuyorsunuz, ancak bu özelliği kullanmıyorsunuz.

Örneğin, hizmet katmanı zaman uyumsuz çağrıları destekleyen Entity Framework ile DB işlemleri gerçekleştiriyorsa:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    using (db = myDBContext.Get()) {
      var list = await db.Countries.Where(condition).ToListAsync();

       return list;
    }
}

Çalışan iş parçacığının db sorgulanırken başka bir şey yapmasına izin verirsiniz (ve böylece başka bir isteği işleyebilir).

Await, tamamen aşağı inmesi gereken bir şey olma eğilimindedir: mevcut bir sisteme uyum sağlamak çok zordur.


-1

Zaman uyumsuz / beklemeden etkili bir şekilde yararlanmıyorsunuz çünkü eşzamanlı yöntemi çalıştırırken istek iş parçacığı engellenecekReturnAllCountries()

Bir isteği işlemek için atanan iş parçacığı, çalışırken boşta bekliyor olacaktır ReturnAllCountries().

ReturnAllCountries()Eşzamansız olacak şekilde uygulayabilirseniz , ölçeklenebilirlik avantajları görürsünüz. Bunun nedeni, iş parçacığının ReturnAllCountries()yürütülürken başka bir isteği işlemek için .NET iş parçacığı havuzuna geri gönderilebilmesidir . Bu, iş parçacıklarını daha verimli kullanarak hizmetinizin daha yüksek verim elde etmesini sağlar.


Bu doğru değil. Soket bağlanır, ancak isteği işleyen iş parçacığı, bir hizmet çağrısını beklerken farklı bir isteği işlemek gibi başka bir şey yapabilir.
Garr Godfrey

-8

Hizmet katmanınızı şu şekilde değiştirirdim:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    return Task.Run(() =>
    {
        return _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
    }      
}

elinizde olduğu gibi, _service.Processaramanızı hala eşzamanlı olarak yürütüyorsunuz ve beklemekten çok az fayda sağlıyor veya hiç kazanmıyorsunuz.

Bu yaklaşımla, potansiyel olarak yavaş aramayı a Taskile sarmalıyorsunuz, başlatıyorsunuz ve beklenmek üzere geri getiriyorsunuz. Şimdi beklemenin avantajını elde edersiniz Task.


3
Blog bağlantısına göre , Task.Run kullansak bile yöntemin hala senkronize olarak çalıştığını görebiliyordum.
arp

Hmm. Yukarıdaki kod ile bu bağlantı arasında pek çok fark var. Sizin Service, eşzamansız bir yöntem değildir ve Serviceçağrının kendisi içinde beklenmemektedir . Demek istediğini anlayabiliyorum ama burada geçerli olduğuna inanmıyorum.
Jonesopolis

8
Task.RunASP.NET üzerinde kaçınılmalıdır. Bu yaklaşım tüm yararlanmak kaldıracak async/ awaitve aslında sadece bir senkron çağrı daha yük altında kötü performans.
Stephen Cleary
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.