2012-12-15 6 views
9

Wiem, że czekanie nie może być stosowane w klauzuli catch. Jednak ja naprawdę nie pojawił się kolejny problem związany z tym, aż do teraz ...czekać na klauzulę catch. Poszukuję pracy w okolicy

Zasadniczo mam warstwa, która jest odpowiedzialna za odbieranie przychodzących żądań, ich przetwarzania, budowanie wiadomości od nich i przekazywać wiadomości do innej warstwy odpowiedzialny za wysyłanie wiadomości.

Jeśli coś pójdzie nie tak podczas wysyłania wiadomości, zostanie zgłoszony wyjątek niestandardowy i zostanie przechwycony przez warstwę wysyłania wiadomości. W tym momencie rekord błędu dla tego komunikatu powinien zostać wstawiony do DB (zajmuje trochę czasu, więc asynchronicznie), a wyjątek powinien być propagowany do górnej warstwy, która jest odpowiedzialna za wysłanie odpowiedzi błędu do klienta, który wysłał żądanie .

Oto kod bardzo uproszczona W celu zilustrowania poniżej:

public async Task ProcessRequestAsync(Request req) 
{ 
    int statusCode = 0; 

    try 
    {  
     await SendMessageAsync(new Message(req));   
    } 
    catch(MyCustomException ex) 
    { 
     statusCode = ex.ErrorCode; 
    } 

    await SendReponseToClientAsync(req, statusCode); 
} 


public async Task SendMessageAsync(Message msg) 
{ 
    try 
    {   
     // Some async operations done here 
    } 
    catch (MyCustomException ex) 
    {  
     await InsertFailureForMessageInDbAsync(msg, ex.ErrorCode); // CAN'T DO THAT 
     throw; 
    } 
} 

Oczywiście warstwa przetwarzanie żądania wie nic o DB, to tylko za budowę wiadomość, przekazując wiadomość do wiadomości warstwę przetwarzania i wysłać odpowiedź do klienta (pozytywną lub negatywną).

Więc myślę, że to ma sens ... Jeśli wystąpi wyjątek, moja "biznesowa" warstwa chce wstawić rekord błędu w DB i ponownie rzucić wyjątek, aby warstwa przetwarzania "żądania" mogła zrobić to, co konieczne (w tym kontekst, wyślij negatywną odpowiedź do klienta).

Czy nie należy stosować wyjątków w ten sposób? Wydaje mi się to czyste, ale fakt, że nie mogę tego zrobić w klauzuli catch, pozwala mi sądzić, że może to być zapach kodu z projektem (nawet gdybym miał pomysł obsługi wyjątku w jednej warstwie, a następnie ponowne wyrzucenie go za górna warstwa, aby wykonać różne operacje, jak również brzmi dla mnie, tak jak to jest dokładnie to, do czego są tworzone wyjątki).

Czy istnieje jakiś pomysł?

Dzięki!

+1

Wygląda na to, że możesz wychwycić wyjątki w warstwie wysyłkowej i po prostu zwrócić obiekt statusu do osoby dzwoniącej. Można nawet utworzyć jedno pole tego obiektu statusu typu 'wyjątek', aby w razie potrzeby można go było ponownie wysłać później. – dlev

+1

Dlaczego po prostu nie zadzwonisz do tej funkcji i nie czekasz na nią? – Rafael

+0

@dlev: Dzięki za pomysł, nadal nie jest mi bardzo miło mieszać powracający status w niektórych miejscach i używać wyjątku w innym miejscu. Należy dokonać wyboru między zwracaniem kodu statusu (lub obiektu) lub wyjątku wyrzucania, ale nie mieszanie obu (to jest to, co pamiętam z "dobrych" wytycznych projektowych dotyczących zgłaszania błędów). – darkey

Odpowiedz

6

Wpadłem też na to kilka razy.

Jak skomentował Rafael, można po prostu zignorować wynik InsertFailureForMessageInDbAsync:

public async Task SendMessageAsync(Message msg) 
{ 
    try 
    {   
    // Some async operations done here 
    } 
    catch (MyCustomException ex) 
    {  
    var _ = InsertFailureForMessageInDbAsync(msg, ex.ErrorCode); 
    throw; 
    } 
} 

pamiętać, że wszelkie wyjątki od InsertFailureForMessageInDbAsync będzie domyślnie ignorowany.

Twoja druga opcja jest bardziej złożona:

public async Task DoSendMessageAsync(Message msg) 
{ 
    // Some async operations done here 
} 

public async Task SendMessageAsync(Message msg) 
{ 
    var task = DoSendMessageAsync(msg); 
    MyCustomException exception = null; 
    try 
    { 
    await task; 
    return; 
    } 
    catch (MyCustomException ex) 
    { 
    exception = ex; 
    } 

    await Task.WhenAll(task, InsertFailureForMessageInDbAsync(msg, exception.ErrorCode)); 
} 

Będzie asynchronicznie obsłużyć wyjątek i zwrócić Task że ma prawdziwy AggregateExption (zawierający zarówno wyjątki jeśli InsertFailureForMessageInDbAsync nie rzucać jeden).

Niestety, await zignoruje drugi wyjątek. Jeśli naprawdę chcesz wszystkie wyjątki przekazywane w górę, można zastąpić ostatni wiersz (await Task.WhenAll...) coś takiego:

Exception exception2 = null; 
try 
{ 
    await InsertFailureForMessageInDbAsync(msg, exception.ErrorCode); 
} 
catch (Exception ex) 
{ 
    exception2 = ex; 
} 

if (exception2 == null) 
    throw new AggregateException(exception); 
else 
    throw new AggregateException(exception, exception2); 

Ale to dość skomplikowane, a nie dokładnie ten rodzaj wzorca, który ma być powtarzany. Jeśli to możliwe, po prostu zignorowałbym wynik logowania, jak zalecił Rafael.

+0

Nice Stephen! Powiedziałbym, że liczyłem na o wiele łatwiejszą drogę do tego, więc nie poszedłem tak daleko, jak to zrobiliście w refleksji. Ale przecież nie ma łatwego sposobu, aby sobie z tym poradzić;) W każdym razie wielkie dzięki za odpowiedź, myślę, że po prostu pójdę prostszą drogą, ale przynajmniej będę wiedział, jak przejść "ciężko", jeśli naprawdę potrzebne. – darkey