Zaman uyumsuz bir geçersizlik yöntemi tarafından oluşturulan bir istisna yakalayın


291

Microsoft'tan .NET için zaman uyumsuz CTP'yi kullanarak, çağırma yönteminde zaman uyumsuz bir yöntem tarafından atılan bir istisnayı yakalamak mümkün müdür?

public async void Foo()
{
    var x = await DoSomethingAsync();

    /* Handle the result, but sometimes an exception might be thrown.
       For example, DoSomethingAsync gets data from the network
       and the data is invalid... a ProtocolException might be thrown. */
}

public void DoFoo()
{
    try
    {
        Foo();
    }
    catch (ProtocolException ex)
    {
          /* The exception will never be caught.
             Instead when in debug mode, VS2010 will warn and continue.
             The deployed the app will simply crash. */
    }
}

Yani temelde asenkron koddaki istisnanın, eğer bu mümkünse, çağrı koduma dönüşmesini istiyorum.


1
Bu size herhangi bir yardımcı olur mu? social.msdn.microsoft.com/Forums/en/async/thread/…
svrist

23
Gelecekte birilerinin buna rastlaması durumunda, Async / Await Best Practices ... makalesinin iyi bir açıklaması "Şekil 2 Bir Async Void Metodundan Alınan İstisnalar Yakalanamaz". " Eşzamansız Task veya eşzamansız Task <T> yönteminden bir istisna atıldığında, bu istisna yakalanır ve Task nesnesine yerleştirilir. Zaman uyumsuz void yöntemlerinde, Task nesnesi yoktur, zaman uyumsuz void yönteminden atılan istisnalar doğrudan eşzamansız geçersizlik yöntemi başladığında etkin olan SynchronizationContext üzerinde yükseltilecek. "
Bay Moose

Sen kullanabilirsiniz bu yaklaşımı ya bu
Tselofan

Yanıtlar:


272

Okumak biraz tuhaf ama evet, istisna arama koduna dönüşecek - ancak sadece siz awaitveya Wait()ararsanızFoo .

public async Task Foo()
{
    var x = await DoSomethingAsync();
}

public async void DoFoo()
{
    try
    {
        await Foo();
    }
    catch (ProtocolException ex)
    {
          // The exception will be caught because you've awaited
          // the call in an async method.
    }
}

//or//

public void DoFoo()
{
    try
    {
        Foo().Wait();
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught because you've
             waited for the completion of the call. */
    }
} 

Zaman uyumsuz void yöntemleri farklı hata işleme anlamlarına sahiptir. Zaman uyumsuz Task veya zaman uyumsuz Task yönteminden bir istisna atıldığında, bu istisna yakalanır ve Task nesnesine yerleştirilir. Zaman uyumsuz void yöntemlerinde Task nesnesi yoktur, bu nedenle zaman uyumsuz void yönteminden atılan tüm istisnalar, zaman uyumsuz void yöntemi başladığında etkin olan SynchronizationContext üzerinde doğrudan ortaya çıkar. - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

.Net, yönteminizi eşzamanlı olarak yürütmeye karar verirse, Wait () kullanmanın uygulamanızın engellenmesine neden olabileceğini unutmayın.

Bu açıklama http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions oldukça iyidir - derleyicinin bu sihri başarmak için attığı adımları tartışır.


3
Aslında okumanın basit olduğunu söylüyorum - ama gerçekte neler olup bittiğini biliyorum - bu yüzden beynim gözlerime inanmamamı söylüyor ...
Stuart

8
Foo () yönteminin void yerine Task olarak işaretlenmesi gerektiğini düşünüyorum.
Sornii

5
Bunun bir AggregateException oluşturacağından oldukça eminim. Bu nedenle, bu yanıtta görünen catch bloğu istisnayı yakalayamayacaktır.
xanadont

2
"ama sadece Beklerseniz veya Beklerseniz () Foo'ya çağrı" awaitFoo geçersiz dönerken Foo'ya nasıl çağrı yapabilirsiniz ? async void Foo(). Type void is not awaitable?
rism

3
Void yöntemi beklenemez, değil mi?
Hitesh P

74

İstisnanın yakalanmamasının nedeni, Foo () yönteminin bir void dönüş türüne sahip olmasıdır ve bu nedenle await çağrıldığında, basitçe geri döner. DoFoo (), Foo'nun tamamlanmasını beklemediğinden, istisna işleyici kullanılamaz.

Bu, yöntem imzalarını değiştirebiliyorsanız daha basit bir çözüm açar - Foo()türü döndürmek için değiştirin Taskve ardından bu kodda olduğu gibi DoFoo()yapabilir await Foo():

public async Task Foo() {
    var x = await DoSomethingThatThrows();
}

public async void DoFoo() {
    try {
        await Foo();
    } catch (ProtocolException ex) {
        // This will catch exceptions from DoSomethingThatThrows
    }
}

20
Bu size gerçekten gizlice yaklaşabilir ve derleyici tarafından uyarılmalıdır.
GGleGrand

20

Kodunuz düşündüğünüz şeyi yapmıyor. Zaman uyumsuz yöntemler, yöntem zaman uyumsuz sonucu beklemeye başladıktan hemen sonra geri döner. Kodun gerçekte nasıl davrandığını araştırmak için izlemeyi kullanmak yararlıdır.

Aşağıdaki kod şunları yapar:

  • 4 görev oluştur
  • Her görev eşzamansız olarak bir sayıyı artırır ve artan sayıyı döndürür
  • Eşzamansız sonuç geldiğinde izlenir.

 

static TypeHashes _type = new TypeHashes(typeof(Program));        
private void Run()
{
    TracerConfig.Reset("debugoutput");

    using (Tracer t = new Tracer(_type, "Run"))
    {
        for (int i = 0; i < 4; i++)
        {
            DoSomeThingAsync(i);
        }
    }
    Application.Run();  // Start window message pump to prevent termination
}


private async void DoSomeThingAsync(int i)
{
    using (Tracer t = new Tracer(_type, "DoSomeThingAsync"))
    {
        t.Info("Hi in DoSomething {0}",i);
        try
        {
            int result = await Calculate(i);
            t.Info("Got async result: {0}", result);
        }
        catch (ArgumentException ex)
        {
            t.Error("Got argument exception: {0}", ex);
        }
    }
}

Task<int> Calculate(int i)
{
    var t = new Task<int>(() =>
    {
        using (Tracer t2 = new Tracer(_type, "Calculate"))
        {
            if( i % 2 == 0 )
                throw new ArgumentException(String.Format("Even argument {0}", i));
            return i++;
        }
    });
    t.Start();
    return t;
}

İzleri gözlemlediğinde

22:25:12.649  02172/02820 {          AsyncTest.Program.Run 
22:25:12.656  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.657  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 0    
22:25:12.658  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.659  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.659  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 1    
22:25:12.660  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 2    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 3    
22:25:12.664  02172/02756          } AsyncTest.Program.Calculate Duration 4ms   
22:25:12.666  02172/02820          } AsyncTest.Program.Run Duration 17ms  ---- Run has completed. The async methods are now scheduled on different threads. 
22:25:12.667  02172/02756 Information AsyncTest.Program.DoSomeThingAsync Got async result: 1    
22:25:12.667  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 8ms    
22:25:12.667  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.665  02172/05220 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 0   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.668  02172/02756 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 2   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.724  02172/05220          } AsyncTest.Program.Calculate Duration 66ms      
22:25:12.724  02172/02756          } AsyncTest.Program.Calculate Duration 57ms      
22:25:12.725  02172/05220 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 0  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 106    
22:25:12.725  02172/02756 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 2  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 0      
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 70ms   
22:25:12.726  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   
22:25:12.726  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.726  02172/05220          } AsyncTest.Program.Calculate Duration 0ms   
22:25:12.726  02172/05220 Information AsyncTest.Program.DoSomeThingAsync Got async result: 3    
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   

Sadece bir çocuk iş parçacığı tamamlandığında (2756) Run yönteminin 2820 iş parçacığında tamamlandığını göreceksiniz. Eğer await metodunuzun etrafına bir dene / yakala koyarsanız, istisnayı olağan şekilde "yakalayabilirsiniz", ancak kodunuz, hesaplama görevi tamamlandığında ve devam ettirmeniz çalıştırıldığında başka bir iş parçacığında yürütülür.

ApiChange aracından ApiChange.Api.dll'yi kullandığım için hesaplama yöntemi atılan istisnayı otomatik olarak izler . İzleme ve Yansıtıcı, neler olup bittiğini anlamanıza çok yardımcı olur. İş parçacığı oluşturmadan kurtulmak için kendi GetAwaiter BeginAwait ve EndAwait sürümlerinizi oluşturabilir ve bir görevi değil, örneğin bir Lazy ve kendi uzatma yöntemleriniz içinde izleme yapabilirsiniz. Ardından, derleyicinin ve TPL'nin ne yaptığını çok daha iyi anlayacaksınız.

Şimdi, herhangi bir istisnadan yayılacak bir yığın çerçevesi kalmadığından, istisnanızı geri almanın bir yolu olmadığını görüyorsunuz. Eşzamansız işlemleri başlattıktan sonra kodunuz tamamen farklı bir şey yapıyor olabilir. Thread.Sleep'i çağırabilir veya hatta sona erebilir. Bir ön plan iş parçacığı kaldığı sürece, uygulamanız mutlu bir şekilde eşzamansız görevleri yürütmeye devam edecektir.


Zaman uyumsuz işleminiz bittikten ve UI iş parçacığına geri çağırdıktan sonra zaman uyumsuz yöntem içindeki istisnayı işleyebilirsiniz. Bunu yapmanın önerilen yolu TaskScheduler.FromSynchronizationContext'tir . Bu, yalnızca bir UI iş parçacığınız varsa ve diğer şeylerle çok meşgul değilse işe yarar.


5

Zaman uyumsuz bir yöntemde void dönüş türünüz varsa, istisnanın kronolojik yığın izini kaybedeceğinizi de unutmamak önemlidir. Görevi aşağıdaki gibi döndürmenizi tavsiye ederim. Hata ayıklamayı çok daha kolay hale getirecek.

public async Task DoFoo()
    {
        try
        {
            return await Foo();
        }
        catch (ProtocolException ex)
        {
            /* Exception with chronological stack trace */     
        }
    }

Bu, tüm yolların bir değer döndürmediği bir soruna neden olacaktır, çünkü bir istisna varsa, denemede var iken değer döndürülmez. İfadeniz yoksa return, bu kod çalışır, çünkü Taskkullanılarak "örtük olarak" döndürülür async / await.
Matias Grioni

4

İstisna, eşzamansız işlevde yakalanabilir.

public async void Foo()
{
    try
    {
        var x = await DoSomethingAsync();
        /* Handle the result, but sometimes an exception might be thrown
           For example, DoSomethingAsync get's data from the network
           and the data is invalid... a ProtocolException might be thrown */
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught here */
    }
}

public void DoFoo()
{
    Foo();
}

2
Hey, biliyorum ama bu bilgiye DoFoo'da gerçekten ihtiyacım var, böylece bilgileri kullanıcı arayüzünde görüntüleyebilirim. Bu durumda, kullanıcı arayüzünün istisnayı görüntülemesi önemlidir, çünkü bu bir son kullanıcı aracı değil, bir iletişim protokolünde hata ayıklama aracıdır
TimothyP

Bu durumda, geri aramalar çok mantıklı. (Eski zaman uyumsuz delegeler)
Sanjeevakumar Hiremath

@Tim: Atılan istisnaya ihtiyacınız olan bilgileri dahil edilsin mi?
Eric J.

2

Bu blog, sorununuzu düzgün bir şekilde Async En İyi Uygulamaları açıklamaktadır .

İşin özü, zaman uyumsuz bir yöntemin dönüşü olarak void'i kullanmamalısınız, bir zaman uyumsuz olay işleyicisi olmadığı sürece, bu kötü bir uygulamadır çünkü istisnaların yakalanmasına izin vermez ;-).

En iyi uygulama, dönüş türünü Görev olarak değiştirmektir. Ayrıca, tüm yol boyunca eşzamansız kodlamayı deneyin, her zaman uyumsuz yöntem çağrısını yapın ve eşzamansız yöntemlerden çağrılmayı deneyin. Konsoldaki zaman uyumsuz olamayan Main yöntemi hariç (C # 7.1'den önce).

Bu en iyi uygulamayı göz ardı ederseniz, GUI ve ASP.NET uygulamalarında kilitlenmelerle karşılaşacaksınız. Kilitlenme, bu uygulamaların yalnızca bir iş parçacığına izin veren ve onu eşzamansız iş parçacığına bırakmayan bir bağlamda çalışması nedeniyle oluşur. Bu, GUI'nin bir dönüş için eşzamanlı olarak beklediği, zaman uyumsuz yöntem ise bağlam: kilitlenmeyi beklediği anlamına gelir.

Bu davranış, bir iş parçacığı havuzu bağlamında çalıştığı için bir konsol uygulamasında gerçekleşmez. Zaman uyumsuz yöntem, planlanacak başka bir iş parçacığında geri dönecektir. Bu nedenle bir test konsolu uygulaması çalışacak, ancak aynı çağrılar diğer uygulamalarda kilitlenecektir ...


1
"Konsoldaki zaman uyumsuz olamayan Main yöntemi hariç." C # 7.1'den bu yana, Main artık eşzamansız bir yöntem bağlantısı olabilir
Adam
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.