Try / catch / nihayetinde beklemek için iyi bir çözüm mü?


94

İstisnayı (yığın izlemesi ile) tekrar atmadan önce asyncbir catchblokta bir yöntemi çağırmam gerekiyor :

try
{
    // Do something
}
catch
{
    // <- Clean things here with async methods
    throw;
}

Ama maalesef awaitbir catchveya finallyblokta kullanamazsınız . Bunu öğrendim çünkü derleyicinin catchsizin awaittalimatınızdan sonra olanı yürütmek için bir bloğa geri dönme yolu yok ...

Ben kullanmaya çalıştı Task.Wait()yerine awaitve bir kilitlenme var. Web'de bundan nasıl kaçınabileceğimi araştırdım ve bu siteyi buldum .

asyncYöntemleri değiştiremediğim ve kullanıp kullanmadıklarını bilmediğim için ConfigureAwait(false), Func<Task>farklı bir iş parçacığı üzerindeyken (kilitlenmeyi önlemek için) bir zaman uyumsuz yöntemi başlatan ve tamamlanmasını bekleyen şu yöntemleri oluşturdum :

public static void AwaitTaskSync(Func<Task> action)
{
    Task.Run(async () => await action().ConfigureAwait(false)).Wait();
}

public static TResult AwaitTaskSync<TResult>(Func<Task<TResult>> action)
{
    return Task.Run(async () => await action().ConfigureAwait(false)).Result;
}

public static void AwaitSync(Func<IAsyncAction> action)
{
    AwaitTaskSync(() => action().AsTask());
}

public static TResult AwaitSync<TResult>(Func<IAsyncOperation<TResult>> action)
{
    return AwaitTaskSync(() => action().AsTask());
}

Yani sorum şu: Bu kodun uygun olduğunu düşünüyor musunuz?

Elbette, bazı geliştirmeleriniz varsa veya daha iyi bir yaklaşım biliyorsanız, dinliyorum! :)


2
Kullanılması awaitcatch bloğu içinde aslında (aşağıda benim cevabını bakın) C # 6.0 beri izin verilir
Adi Lester

3
İlgili C # 5.0 hata iletileri: CS1985 : Yakalama cümlesinin gövdesinde beklenemez . CS1984 : Nihayet cümlesinin gövdesinde
DavidRR

Yanıtlar:


174

Mantığı catchbloğun dışına taşıyabilir ve gerekirse kullanarak istisnayı yeniden atabilirsiniz ExceptionDispatchInfo.

static async Task f()
{
    ExceptionDispatchInfo capturedException = null;
    try
    {
        await TaskThatFails();
    }
    catch (MyException ex)
    {
        capturedException = ExceptionDispatchInfo.Capture(ex);
    }

    if (capturedException != null)
    {
        await ExceptionHandler();

        capturedException.Throw();
    }
}

Bu şekilde, arayan istisnanın StackTraceözelliğini incelediğinde, istisnanın içinde nereye TaskThatFailsatıldığını kaydetmeye devam eder.


1
(Stephen Cleary'nin cevabında olduğu gibi) ExceptionDispatchInfoyerine tutmanın avantajı nedir Exception?
Varvara Kalinina

2
Tahmin edebiliyorum ki, onu yeniden atmaya karar verirseniz, öncekilerin Exceptionhepsini kaybedersiniz StackTrace?
Varvara Kalinina

1
@VarvaraKalinina Kesinlikle.

54

C # 6.0'dan beri, awaitin catchve finallyblokları kullanmanın mümkün olduğunu bilmelisiniz , böylece aslında bunu yapabilirsiniz:

try
{
    // Do something
}
catch (Exception ex)
{
    await DoCleanupAsync();
    throw;
}

One Biraz önce bahsettiğim gibi yeni C # 6.0 özellikleri burada listelenmiştir veya video olarak burada .


C # 6.0'daki bekle / son olarak bloklar için destek Wikipedia'da da belirtilmiştir.
DavidRR

4
@DavidRR. Wikipedia otoriter değildir. Milyonlarca kişi arasında bu konudaki başka bir web sitesidir.
user34660

Bu C # 6 için geçerliyken, soru en başından C # 5 olarak etiketlendi. Bu, bu cevabı burada bulmanın kafa karıştırıcı olup olmadığını veya bu durumlarda sadece belirli sürüm etiketini kaldırmamız gerekip gerekmediğini merak etmeme neden oluyor.
julealgon

16

asyncHata işleyicileri kullanmanız gerekiyorsa, şuna benzer bir şey tavsiye ederim:

Exception exception = null;
try
{
  ...
}
catch (Exception ex)
{
  exception = ex;
}

if (exception != null)
{
  ...
}

asyncKodu eşzamanlı olarak engellemenin sorunu (hangi iş parçacığı üzerinde çalıştığına bakılmaksızın) eşzamanlı olarak engellemenizdir. Çoğu senaryoda kullanmak daha iyidir await.

Güncelleme: Yeniden atmanız gerektiğinden kullanabilirsiniz ExceptionDispatchInfo.


1
Teşekkür ederim ama maalesef bu yöntemi zaten biliyorum. Normalde yaptığım bu, ama bunu burada yapamam. Ben sadece kullanırsanız throw exception;içinde ifdeyimi yığın izleme kaybolacaktır.
user2397050

3

Projemizde aşağıdaki yeniden kullanılabilir yardımcı program sınıfına hvd'nin harika cevabını çıkardık :

public static class TryWithAwaitInCatch
{
    public static async Task ExecuteAndHandleErrorAsync(Func<Task> actionAsync,
        Func<Exception, Task<bool>> errorHandlerAsync)
    {
        ExceptionDispatchInfo capturedException = null;
        try
        {
            await actionAsync().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            capturedException = ExceptionDispatchInfo.Capture(ex);
        }

        if (capturedException != null)
        {
            bool needsThrow = await errorHandlerAsync(capturedException.SourceException).ConfigureAwait(false);
            if (needsThrow)
            {
                capturedException.Throw();
            }
        }
    }
}

Bunu şu şekilde kullanırsınız:

    public async Task OnDoSomething()
    {
        await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync(
            async () => await DoSomethingAsync(),
            async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; }
        );
    }

Adlandırmayı geliştirmekte özgürsünüz, kasıtlı olarak ayrıntılı tuttuk. Çağrı sitesinde zaten yakalandığından, sarıcı içindeki bağlamı yakalamaya gerek olmadığını unutmayın ConfigureAwait(false).

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.