async / await - Görev vs boşluğa ne zaman dönmeli?


502

Hangi senaryolarda kullanmak istersiniz

public async Task AsyncMethod(int num)

onun yerine

public async void AsyncMethod(int num)

Düşünebileceğim tek senaryo, ilerlemesini takip edebilmek için göreve ihtiyacınız varsa.

Ayrıca, aşağıdaki yöntemde, zaman uyumsuz ve bekleyen anahtar kelimeler gereksiz mi?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

20
Async yöntemlerinin her zaman Async adıyla eklenmesi Foo()gerektiğini unutmayın.Örnek olur FooAsync().
Fred

30
@Fred Çoğunlukla, ama her zaman değil. Bu sadece sözleşmedir ve bu sözleşmenin kabul edilen istisnaları olaya dayalı sınıflar veya arayüz sözleşmeleriyle ilgilidir, bkz. MSDN . Örneğin, Button1_Click gibi yaygın olay işleyicilerini yeniden adlandırmamalısınız.
Ben

14
Bunun yerine Thread.Sleepgörevlerinizle birlikte kullanmamanız gereken bir notawait Task.Delay(num)
Bob Vale

45
@fred Bunu kabul etmiyorum, zaman uyumsuz bir sonek ekleyen IMO yalnızca hem senkronizasyon hem de zaman uyumsuzluk seçeneklerine sahip bir arayüz sağlarken kullanılmalıdır. Tek bir amaç olduğunda şeyleri zaman uyumsuz olarak adlandırmak anlamsızdır. Vaka , görevdeki tüm yöntemlerin Async Task.Delayolmadığı Task.AsyncDelaygibi
değil

11
Bu sabah bir webapi 2 denetleyici yöntemiyle ilginç bir sorun yaşadım, bunun async voidyerine ilan edildi async Task. Yöntem, denetleyicinin bir üyesi olarak bildirilen bir Entity Framework bağlam nesnesi kullandığından, yöntem, yürütme işlemi tamamlanmadan önce atılmış olduğu için çöktü. Çerçeve, denetleyiciyi yöntemi yürütülmeden bitirdi. Yöntemi zaman uyumsuz Görev olarak değiştirdim ve işe yaradı.
costa

Yanıtlar:


417

1) Normalde geri dönmek istersiniz Task. Ana istisna , bir dönüş türüne (etkinlikler için) ihtiyacınız olduğunda olmalıdır void. Arayan kişinin awaitgörevinize sahip olmasına izin vermemek için bir neden yoksa, neden izin vermeyin?

2) asyncgeri dönen yöntemler voidbaşka bir açıdan özeldir: üst düzey asenkron işlemlerini temsil ederler ve göreviniz bir istisna döndürdüğünde devreye giren ek kurallara sahiptirler. En kolay yol, farkı bir örnekle göstermektir:

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

fistisnası her zaman "gözlenir". Üst düzey eşzamansız bir yöntem bırakan bir istisna, diğer işlenmemiş istisnalar gibi ele alınır. gistisnası asla gözlenmez. Çöp toplayıcı görevi temizlemek için geldiğinde, görevin bir istisnayla sonuçlandığını görür ve kimse istisnayı ele almaz. Bu olduğunda, TaskScheduler.UnobservedTaskExceptionişleyici çalışır. Bunun olmasına asla izin vermemelisin. Örneğinizi kullanmak için,

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

Evet, kullanın asyncve awaitburada, bir istisna atılırsa yönteminizin hala doğru çalıştığından emin olurlar.

daha fazla bilgi için bkz. http://msdn.microsoft.com/en-us/magazine/jj991977.aspx


10
Benim yorumum fyerine demek gistedim. Dan istisna fDİR geçirilen SynchronizationContext. gyükseltilir UnobservedTaskException, ancak UTEişlem yapılmazsa işlem artık çökmez. Bu gibi göz ardı edilen "eşzamansız istisnaların" kabul edilebilir olduğu bazı durumlar vardır.
Stephen Cleary

3
Eğer varsa WhenAnybirden sahip Taskler istisnalar neden olur. Genellikle ilkini işlemek zorunda kalırsınız ve diğerlerini de görmezden gelmek istersiniz.
Stephen Cleary

1
@StephenCleary Teşekkürler, sanırım bu iyi bir örnek, ancak WhenAnydiğer istisnaları görmezden gelmenin uygun olup olmadığı konusunda ilk önce arama nedeninize bağlıdır : bunun için sahip olduğum ana kullanım durumu, bitirildiğinde kalan görevleri hala beklemektedir. , istisnasız veya istisnasız.

10
Boşluk yerine bir Görevi iade etmeyi neden önerdiğiniz konusunda biraz kafam karıştı. Dediğiniz gibi, f () fırlatıp istisna yapacak, ancak g () çarpmayacaktır. Bu arka plan iş parçacığı istisnalarından haberdar olmak en iyisi değil mi?
user981225

2
@ user981225 Gerçekten, ama o zaman g'nin arayanın sorumluluğu haline gelir: g'yi çağıran her yöntem zaman uyumsuz olmalı ve kullanımı da beklemelidir. Bu bir kılavuzdur, zor bir kural değildir, özel programınızda g dönüş boşluğunun daha kolay olduğuna karar verebilirsiniz.

40

Jérôme Laban hakkında yazdığı asyncve voidyazdığı bu çok yararlı makaleye rastladım : https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void .html

Sonuç olarak async+void, sistemin çökmesine neden olabilir ve genellikle yalnızca kullanıcı arabirimi tarafı olay işleyicilerinde kullanılmalıdır.

Bunun nedeni, AsyncVoidMethodBuilder tarafından kullanılan ve bu örnekte hiçbiri olmayan Senkronizasyon Bağlamı'dır. Ortam Senkronizasyon Bağlamı olmadığında, zaman uyumsuz void yönteminin gövdesi tarafından işlenmeyen herhangi bir istisna ThreadPool üzerinde yeniden oluşturulur. Bu tür işlenmeyen istisnaların atılabileceği başka hiçbir mantıksal yer olmasa da, işin talihsiz etkisi, işlemin sonlandırılmasıdır, çünkü ThreadPool'daki işlenmeyen istisnalar, .NET 2.0'dan bu yana işlemi etkili bir şekilde sonlandırır. AppDomain.UnhandledException olayını kullanarak işlenmeyen tüm özel durumları kesebilirsiniz, ancak işlemi bu olaydan kurtarmanın bir yolu yoktur.

UI olay işleyicileri yazarken, zaman uyumsuz yöntemler bir şekilde ağrısızdır, çünkü istisnalar zaman uyumsuz yöntemlerde bulunanla aynı şekilde ele alınır; Sevk Görevlisine atılırlar. Bu gibi istisnalardan kurtulma olasılığı vardır, çoğu vaka için daha doğrudur. Bununla birlikte, UI olay işleyicilerinin dışında, eşzamansız geçersiz yöntemlerin kullanımı bir şekilde tehlikelidir ve bulmak o kadar kolay olmayabilir.


Bu doğru mu? Async yöntemlerindeki istisnaların Görev içinde ele alındığını düşündüm. Ve ayrıca afaik daima deafault SynchronizationContext var
NM

30

Bu ifadelerden net bir fikrim var.

  1. Async void yöntemlerinin farklı hata işleme semantiği vardır. Zaman uyumsuz bir Görev veya zaman uyumsuz Görev yönteminden bir istisna atılırsa, bu istisna yakalanır ve Görev nesnesine yerleştirilir. Eşzamansız void yöntemlerinde, Görev nesnesi yoktur, bu nedenle bir eşzamansız void yönteminden atılan tüm özel durumlar doğrudan SynchronizationContext (SynchronizationContext), eşzamansız void yönteminde etkin olan bir "temsil" konumu olabilir. " başladı

Bir Async Void Yönteminden İstisnalar Yakalama ile Yakalanamıyor

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

Bu istisnalar, AppDomain.UnhandledException veya GUI / ASP.NET uygulamaları için benzer bir tümünü yakalama olayı kullanılarak gözlemlenebilir, ancak bu olayları düzenli istisna işleme için kullanmak, sürdürülemezlik için bir reçetedir (uygulamayı kilitler).

  1. Asenkron boşluk yöntemleri farklı oluşturma semantiğine sahiptir. Görev veya Görev döndüren zaman uyumsuz yöntemler, bekle, Görev.WhenAny, Görev.WhenAll vb. Boşluk döndüren zaman uyumsuz yöntemler, tamamladıkları arama kodunu bildirmenin kolay bir yolunu sunmaz. Birkaç asenkron boşluk yöntemini başlatmak kolaydır, ancak ne zaman bittiklerini belirlemek kolay değildir. Async void yöntemleri, başlatıldıklarında ve bittiğinde SynchronizationContext'lerini bilgilendirir, ancak özel bir SynchronizationContext, normal uygulama kodu için karmaşık bir çözümdür.

  2. Eşzamansız olay işleyicileri kullanılırken zaman uyumsuz olay işleyicisini kullanırken yararlı olan Async Void yöntemi, senkronize olay işleyicilerin davranışlarına benzer şekilde doğrudan SynchronizationContext üzerinde

Daha fazla ayrıntı için bu bağlantıyı kontrol edin https://msdn.microsoft.com/en-us/magazine/jj991977.aspx


21

Zaman uyumsuz boşluğu çağırmayla ilgili sorun, görevi geri almamanızdır, işlevin görevinin ne zaman tamamlandığını bilmenin hiçbir yolu yoktur (bkz. Https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/ ? p = 96655 )

Zaman uyumsuz işlevini çağırmanın üç yolu şunlardır:

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

Her durumda, işlev bir görev zincirine dönüştürülür. Fark, işlevin döndürdüğü şeydir.

İlk durumda, işlev sonunda t üreten bir görev döndürür.

İkinci durumda, işlev ürünü olmayan bir görev döndürür, ancak ne zaman tamamlanacağını bilmek için üzerinde bekleyebilirsiniz.

Üçüncü dava kötü olanı. Üçüncü durum, ikinci durum gibidir, ancak görevi geri alamazsınız. İşlevin görevinin ne zaman tamamlandığını bilmenin bir yolu yoktur.

Zaman uyumsuz geçersiz durum "ateşle ve unut" şeklindedir: Görev zincirini başlatırsınız, ancak işin ne zaman bittiğini umursamazsınız. İşlev döndüğünde, bildiğiniz tek şey ilk beklemeye kadar her şeyin yürütülmüş olmasıdır. İlk bekleyişten sonraki her şey, gelecekte erişiminizin olmadığı belirli bir noktada çalışacaktır.


6

async voidİstisnaları yakalamaya dikkat ettiğiniz sürece arka plan işlemlerini başlatmak için de kullanabileceğinizi düşünüyorum . Düşünceler?

class Program {

    static bool isFinished = false;

    static void Main(string[] args) {

        // Kick off the background operation and don't care about when it completes
        BackgroundWork();

        Console.WriteLine("Press enter when you're ready to stop the background operation.");
        Console.ReadLine();
        isFinished = true;
    }

    // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
    static async void BackgroundWork() {
        // It's important to catch exceptions so we don't crash the appliation.
        try {
            // This operation will end after ten interations or when the app closes. Whichever happens first.
            for (var count = 1; count <= 10 && !isFinished; count++) {
                await Task.Delay(1000);
                Console.WriteLine($"{count} seconds of work elapsed.");
            }
            Console.WriteLine("Background operation came to an end.");
        } catch (Exception x) {
            Console.WriteLine("Caught exception:");
            Console.WriteLine(x.ToString());
        }
    }
}

1
Zaman uyumsuz boşluğu çağırmayla ilgili sorun, görevi geri
almamanızdır

Bu, sonucu umursamadığınız ve diğer işlemleri etkilemek için bir istisna istemediğiniz (nadir) arka plan işlemleri için anlamlıdır. Tipik bir kullanım durumu, bir günlük sunucusuna bir günlük olayı göndermek olabilir. Bunun arka planda olmasını istiyorsunuz ve günlük sunucusu çalışmıyorsa hizmet / istek işleyicinizin başarısız olmasını istemiyorsunuz. Tabii ki, tüm istisnaları yakalamanız gerekir, aksi takdirde işleminiz sonlandırılacaktır. Yoksa bunu başarmanın daha iyi bir yolu var mı?
Florian Winter

Bir Async Void Yönteminden İstisnalar Yakalama ile Yakalanamıyor. msdn.microsoft.com/tr-tr/magazine/jj991977.aspx
Si Zi

3
@SiZi yakalama örnekte gösterildiği gibi asenkron boşluk yönteminin İÇİNDEKİ ve yakalanır.
2018

Gereksinimleriniz günlüğe
kaydedemiyorsanız

0

Cevabım basit bir şekilde geçersiz yöntemi bekleyemezsiniz

Error   CS4008  Cannot await 'void' TestAsync   e:\test\TestAsync\TestAsyncProgram.cs

Yöntem asenkron ise daha iyi olmak daha iyidir, çünkü asenkron avantajı kaybedebilirsiniz.


-2

Microsoft belgelerine göre , ASLA kullanmamalısınızasync void

Bunu yapmayın: Aşağıdaki örnek async void, ilk beklemeye ulaşıldığında HTTP isteğinin tamamlanmasını sağlayan kullanır :

  • Hangisi ASP.NET Core uygulamalarında DAİMA kötü bir uygulamadır.

  • HTTP isteği tamamlandıktan sonra HttpResponse'ye erişir.

  • Süreci çöküyor.

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.