Eşzamanlı kodu eşzamansız çağrıya sarma


97

ASP.NET uygulamasında tamamlanması oldukça fazla zaman harcayan bir yöntemim var. Bu yönteme bir çağrı, kullanıcının sağladığı önbellek durumuna ve parametrelere bağlı olarak, bir kullanıcı isteği sırasında en fazla 3 kez gerçekleşebilir. Her aramanın tamamlanması yaklaşık 1-2 saniye sürer. Yöntemin kendisi hizmete eşzamanlı bir çağrıdır ve uygulamayı geçersiz kılma olasılığı yoktur.
Dolayısıyla, hizmete yapılan eşzamanlı çağrı şuna benzer:

public OutputModel Calculate(InputModel input)
{
    // do some stuff
    return Service.LongRunningCall(input);
}

Ve yöntemin kullanımı (not, bu yöntem çağrısı birden fazla olabilir):

private void MakeRequest()
{
    // a lot of other stuff: preparing requests, sending/processing other requests, etc.
    var myOutput = Calculate(myInput);
    // stuff again
}

Bu yöntemin eşzamanlı çalışmasını sağlamak için uygulamayı kendi tarafımdan değiştirmeye çalıştım ve şimdiye kadar geldiğim şey bu.

public async Task<OutputModel> CalculateAsync(InputModel input)
{
    return await Task.Run(() =>
    {
        return Calculate(input);
    });
}

Kullanım ("başka şeyler yap" kodunun bir parçası, hizmet çağrısıyla aynı anda çalışır):

private async Task MakeRequest()
{
    // do some stuff
    var task = CalculateAsync(myInput);
    // do other stuff
    var myOutput = await task;
    // some more stuff
}

Sorum şu. ASP.NET uygulamasında yürütmeyi hızlandırmak için doğru yaklaşımı mı kullanıyorum yoksa senkronize kodu senkronize olmayan bir şekilde çalıştırmaya çalışırken gereksiz iş mi yapıyorum? İkinci yaklaşımın ASP.NET'te neden bir seçenek olmadığını (eğer gerçekten değilse) kimse açıklayabilir mi? Ayrıca, böyle bir yaklaşım uygulanabilirse, şu anda gerçekleştirebileceğimiz tek çağrı ise bu yöntemi eşzamansız olarak çağırmam gerekir mi (Böyle bir durumum var, tamamlanmasını beklerken yapacak başka bir şey yokken)?
Bu konuyla ilgili ağdaki makalelerin çoğu, async-awaitzaten awaitableyöntemler sağlayan kodla yaklaşımın kullanılmasını kapsar , ancak bu benim durumum değil. Burayaparalel aramaların durumunu tanımlamayan, senkronizasyon aramasını sarma seçeneğini reddeden durumumu açıklayan güzel bir makale, ama bence benim durumum tam olarak bunu yapma fırsatı.
Yardım ve ipuçları için şimdiden teşekkürler.

Yanıtlar:


119

İki farklı eşzamanlılık türü arasında bir ayrım yapmak önemlidir. Eşzamansız eşzamanlılık, uçuşta birden çok eşzamansız işleminiz olduğu zamandır (ve her işlem eşzamansız olduğundan hiçbiri aslında bir iş parçacığı kullanmaz ). Paralel eşzamanlılık, her biri ayrı bir işlem yapan birden fazla iş parçacığına sahip olduğunuz zamandır.

Yapılacak ilk şey, bu varsayımı yeniden değerlendirmektir:

Yöntemin kendisi hizmete eşzamanlı bir çağrıdır ve uygulamayı geçersiz kılma olasılığı yoktur.

"Hizmetiniz" bir web hizmeti veya G / Ç'ye bağlı herhangi bir şeyse, en iyi çözüm bunun için bir eşzamansız API yazmaktır.

"Hizmetinizin" web sunucusu ile aynı makinede yürütülmesi gereken CPU'ya bağlı bir işlem olduğu varsayımıyla devam edeceğim.

Durum buysa, değerlendirilecek sonraki şey başka bir varsayımdır:

Daha hızlı işlem yapmak için isteğe ihtiyacım var.

Yapmanız gereken şeyin bu olduğundan kesinlikle emin misiniz? Bunun yerine yapabileceğiniz herhangi bir ön uç değişikliği var mı - örneğin, isteği başlatın ve kullanıcının işlem sırasında başka bir iş yapmasına izin verin?

Evet, bireysel isteğin gerçekten daha hızlı yürütülmesini sağlamanız gerektiği varsayımıyla devam edeceğim.

Bu durumda, web sunucunuzda paralel kod çalıştırmanız gerekecektir. Paralel kod, ASP.NET'in diğer istekleri işlemek için ihtiyaç duyabileceği iş parçacıkları kullanacağından ve iş parçacığı ekleyerek / ekleyerek ASP.NET iş parçacığı buluşsal yöntemini atacağından genel olarak kesinlikle önerilmez. Yani, bu kararın sunucunuzun tamamı üzerinde bir etkisi vardır.

ASP.NET üzerinde paralel kod kullandığınızda, web uygulamanızın ölçeklenebilirliğini gerçekten sınırlamaya karar veriyorsunuz. Ayrıca, özellikle istekleriniz hiç patlamazsa, makul miktarda iş parçacığı karmaşası görebilirsiniz. Eşzamanlı kullanıcı sayısının oldukça düşük olacağını biliyorsanız (yani genel bir sunucu değil) yalnızca ASP.NET üzerinde paralel kod kullanmanızı öneririm .

Yani, bu kadar ileri giderseniz ve ASP.NET üzerinde paralel işlem yapmak istediğinizden eminseniz, o zaman birkaç seçeneğiniz vardır.

Daha kolay yöntemlerden biri Task.Run, mevcut kodunuza çok benzer şekilde kullanmaktır . Ancak, CalculateAsyncişlemin eşzamansız olduğunu (ki bu değil) ima ettiği için bir yöntemin uygulanmasını önermiyorum . Bunun yerine, Task.Runarama noktasında kullanın :

private async Task MakeRequest()
{
  // do some stuff
  var task = Task.Run(() => Calculate(myInput));
  // do other stuff
  var myOutput = await task;
  // some more stuff
}

Sizin kodla iyi çalışır, alternatif olarak kullanabileceğiniz Paralleltürü, yani Parallel.For, Parallel.ForEachya Parallel.Invoke. ParallelKodun avantajı , istek iş parçacığının paralel iş parçacıklarından biri olarak kullanılması ve ardından iş parçacığı bağlamında yürütülmeye devam etmesidir ( asyncörnekten daha az bağlam geçişi vardır ):

private void MakeRequest()
{
  Parallel.Invoke(() => Calculate(myInput1),
      () => Calculate(myInput2),
      () => Calculate(myInput3));
}

ASP.NET üzerinde Parallel LINQ (PLINQ) kullanmanızı hiç önermiyorum.


1
Ayrıntılı bir cevap için çok teşekkürler. Açıklığa kavuşturmak istediğim bir şey daha var. Uygulamayı kullanmaya başladığımda Task.Run, içerik iş parçacığı havuzundan alınan başka bir iş parçacığında çalıştırılıyor. Bu durumda, engelleme çağrılarını (hizmete benim hizmet çağrısı gibi) Runs içine sarmaya gerek kalmaz , çünkü bunlar her zaman her biri bir iş parçacığı tüketecek ve yöntemin yürütülmesi sırasında engellenecektir. Böyle bir durumda async-awaitbenim durumumda kalan tek fayda, aynı anda birkaç eylemde bulunmaktır. Yanılıyorsam lütfen beni düzeltin.
Eadel

2
Evet, Run(veya Parallel) ' den elde ettiğiniz tek fayda eşzamanlılıktır. İşlemlerin her biri hala bir iş parçacığını engelliyor. Servisin bir web servisi olduğunu söylediğiniz için Runveya kullanmanızı önermiyorum Parallel; bunun yerine, hizmet için zaman uyumsuz bir API yazın.
Stephen Cleary

3
@Evleneceksen "... ve her işlem eşzamansız olduğundan, hiçbiri aslında bir iş parçacığı kullanmıyor ..." ile ne demek istiyorsun teşekkürler!
alltej

3
@alltej: Gerçekten eşzamansız kod için iş parçacığı yoktur .
Stephen Cleary

4
@JoshuaFrank: Doğrudan değil. Eşzamanlı bir API'nin arkasındaki kod, çağıran iş parçacığını tanım gereği engellemelidir. Sen olabilir gibi bir zaman uyumsuz API kullanmak BeginGetResponsebakınız - hepsi tamamlandığında, ancak bu tür yaklaşım zor olana kadar (eşzamanlı) blok sonra ve bu birkaç başlatın ve blog.stephencleary.com/2012/07/dont-block-on -async-code.html ve msdn.microsoft.com/en-us/magazine/mt238404.aspx . asyncMümkünse tamamen benimsemek genellikle daha kolay ve daha temizdir .
Stephen Cleary

1

Aşağıdaki kodun bir Görevi her zaman eşzamansız olarak çalışacak şekilde dönüştürebileceğini buldum

private static async Task<T> ForceAsync<T>(Func<Task<T>> func)
{
    await Task.Yield();
    return await func();
}

ve aşağıdaki şekilde kullandım

await ForceAsync(() => AsyncTaskWithNoAwaits())

Bu, herhangi bir Görevi eşzamansız olarak yürütür, böylece bunları WhenAll, WhenAny senaryolarında ve diğer kullanımlarda birleştirebilirsiniz.

Ayrıca, Task.Yield () öğesini aradığınız kodun ilk satırı olarak da ekleyebilirsiniz.

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.