Task.Run komutunu doğru bir şekilde kullandığınızda ve yalnızca eşzamansız beklerken


317

Size ne zaman kullanacağınız konusunda doğru mimari hakkındaki fikrinizi sormak istiyorum Task.Run. WPF .NET 4.5 uygulamamızda (Caliburn Micro framework ile) tembel kullanıcı arayüzü yaşıyorum.

Temelde (çok basitleştirilmiş kod parçacıkları) yapıyorum:

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // Makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync();

      HideLoadingAnimation();
   }
}

public class ContentLoader
{
    public async Task LoadContentAsync()
    {
        await DoCpuBoundWorkAsync();
        await DoIoBoundWorkAsync();
        await DoCpuBoundWorkAsync();

        // I am not really sure what all I can consider as CPU bound as slowing down the UI
        await DoSomeOtherWorkAsync();
    }
}

Okuduğum / gördüğüm makalelerden / videolardan await async, bunun bir arka plan iş parçacığında çalışmadığını ve arka planda çalışmaya başlamak için beklemek zorunda kaldığınızı biliyorum Task.Run(async () => ... ). Kullanmak async awaitUI'yi engellemez, ancak yine de UI iş parçacığında çalışıyor, bu yüzden onu laggy yapıyor.

Task.Run koymak için en iyi yer neresi?

Ben sadece

  1. Dış çağrı sarın çünkü bu .NET için daha az iş parçacığı işi

  2. , ya da Task.Runbaşka yerlerde yeniden kullanılabilir kıldığından, yalnızca dahili olarak çalışan CPU'ya bağlı yöntemleri kaydırmalı mıyım? Burada özde derin arka plan iş parçacıkları çalışmaya başlamak iyi bir fikir olup olmadığından emin değilim.

Reklam (1), ilk çözüm şöyle olacaktır:

public async void Handle(SomeMessage message)
{
    ShowLoadingAnimation();
    await Task.Run(async () => await this.contentLoader.LoadContentAsync());
    HideLoadingAnimation();
}

// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.

Reklam (2), ikinci çözüm şöyle olacaktır:

public async Task DoCpuBoundWorkAsync()
{
    await Task.Run(() => {
        // Do lot of work here
    });
}

public async Task DoSomeOtherWorkAsync(
{
    // I am not sure how to handle this methods -
    // probably need to test one by one, if it is slowing down UI
}

BTW, (1) 'deki satır await Task.Run(async () => await this.contentLoader.LoadContentAsync());basit olmalıdır await Task.Run( () => this.contentLoader.LoadContentAsync() );. AFAIK, bir saniye awaitve asynciçerisi ekleyerek hiçbir şey kazanmazsınız Task.Run. Ve parametreleri geçmediğiniz için, bu biraz daha basitleştirir await Task.Run( this.contentLoader.LoadContentAsync );.
ToolmakerSteve

İçinde ikinci kez bekliyorsanız aslında küçük bir fark var. Bu makaleye bakın . Çok yararlı buldum, sadece bu nokta ile hemfikir değilim ve beklemek yerine doğrudan Görev'e dönmeyi tercih ediyorum. (yorumunda önerdiğin gibi)
Lukas K

Yanıtlar:


365

Not Bir UI iş parçacığı üzerinde çalışmalar yapmak için yönergeleri blogumda toplanan:

  • UI iş parçacığını bir seferde 50ms'den fazla engellemeyin.
  • UI iş parçacığında saniyede ~ 100 devam edebilirsiniz; 1000 çok fazla.

Kullanmanız gereken iki teknik vardır:

1) Yapabildiğiniz ConfigureAwait(false)zaman kullanın .

Örneğin, await MyAsync().ConfigureAwait(false);yerine await MyAsync();.

ConfigureAwait(false)söyler awaitsiz ( "UI iş parçacığı üzerinde" aracı "geçerli bağlam üzerinde", bu durumda) geçerli bağlama devam etmek gerekmez. Ancak, bu asyncyöntemin geri kalanı için (sonrasında ConfigureAwait), geçerli bağlamda olduğunuzu varsayan hiçbir şey yapamazsınız (ör. Kullanıcı arabirimi öğelerini güncelleme).

Daha fazla bilgi için, Eşzamansız Programlamada En İyi Uygulamalar adlı MSDN makaleme bakın .

2) Task.RunCPU bağlantılı yöntemleri çağırmak için kullanın .

Kullanmanız gerekir Task.Run, ancak yeniden kullanılabilir olmasını istediğiniz herhangi bir kod içinde (ör. Kütüphane kodu) kullanılmamalıdır. Kullanmak Yani Task.Runetmek çağrı değil bir parçası olarak, yöntem uygulanması yöntemi.

Yani tamamen CPU bağlantılı işler şöyle görünecektir:

// Documentation: This method is CPU-bound.
void DoWork();

Hangisini kullanarak ararsınız Task.Run:

await Task.Run(() => DoWork());

CPU ve I / O sınırlarının bir karışımı olan yöntemlerin AsyncCPU'ya bağlı niteliklerini gösteren belgelerle bir imzası olmalıdır :

// Documentation: This method is CPU-bound.
Task DoWorkAsync();

Bunu kullanarak da çağırırsınız Task.Run(kısmen CPU'ya bağlı olduğu için):

await Task.Run(() => DoWorkAsync());

4
Hızlı saygınız için teşekkürler! Yayınladığınız ve blogunuzda referans verilen videoları gördüğünüz bağlantıyı biliyorum. Aslında bu yüzden bu soruyu gönderdim - videoda (cevabınızdakiyle aynıdır) söylenir, çekirdek kodunda Task.Run kullanmamalısınız. Ama benim sorunum, cevapları yavaşlatmak için her kullandığımda böyle bir yöntemi sarmak gerekiyor (lütfen tüm kodum zaman uyumsuz ve engelleme değil, ama Thread.Run olmadan sadece laggy olduğunu unutmayın). Ben de sadece CPU bağlı yöntemleri (birçok Task.Run çağrıları) veya tamamen bir Task.Run her şeyi sarmak için daha iyi bir yaklaşım olup olmadığını karıştı.
Lukas K

12
Tüm kütüphane yöntemleriniz kullanılmalıdır ConfigureAwait(false). İlk önce bunu yaparsanız, bunun Task.Runtamamen gereksiz olduğunu görebilirsiniz. Eğer varsa do hala ihtiyaç Task.Run, o zaman, bir kez ya da birçok kez diyoruz olmadığını, bu durumda çalışma zamanında çok fark yaratmaz bu yüzden sadece kod için en doğal olanı yap.
Stephen Cleary

2
İlk tekniğin ona nasıl yardım edeceğini anlamıyorum. ConfigureAwait(false)Cpu-bağlı yönteminizde kullansanız bile , hala cpu-bağlı yöntemi yapacak olan UI iş parçacığıdır ve bir TP iş parçacığında yalnızca bundan sonraki her şey yapılabilir. Yoksa bir şeyi yanlış mı anladım?
Darius

4
@ user4205580: Hayır, Task.Runeşzamansız imzaları anlar, bu yüzden tamamlanana kadar DoWorkAsynctamamlanmaz. Ekstra async/ awaitgereksiz. Blog Task.Rundizimdeki görgü kuralları hakkında "neden" in daha fazlasını açıklıyorum .
Stephen Cleary

3
@ user4205580: Hayır. "Temel" zaman uyumsuz yöntemlerin büyük çoğunluğu bunu dahili olarak kullanmaz. Normal bir "çekirdek" uyumsuz yöntemi uygulamak için bir yol kullanmaktır TaskCompletionSource<T>veya olarak kısaltılmış işaretlerden biri FromAsync. Async yöntemleri neden iş parçacığı gerektirmez daha ayrıntılı gider bir blog yazısı var .
Stephen Cleary

12

ContentLoader'ınızla ilgili bir sorun, dahili olarak sıralı olarak çalışmasıdır. Daha iyi bir desen işi paralel hale getirip sonunda senkronize etmektir, böylece

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync(); 

      HideLoadingAnimation();   
   }
}

public class ContentLoader 
{
    public async Task LoadContentAsync()
    {
        var tasks = new List<Task>();
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoIoBoundWorkAsync());
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoSomeOtherWorkAsync());

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }
}

Açıktır ki, görevlerden herhangi biri daha önceki diğer görevlerden veri gerektiriyorsa işe yaramaz, ancak çoğu senaryo için size daha iyi genel verim sağlayacaktır.

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.