Поймать исключение из `await` при поддержании возврата await во внешнюю область - PullRequest
1 голос
/ 25 сентября 2019

У меня есть хороший линейный код, включенный async await, который возвращает значение.Большая часть моего кода идет по этому шаблону:

var a = await Foo1();
Bar1(a);
Bar2(a);
Bar3(a);

var b = await Foo2(a);
Bar4(a,b);

Но тогда мне нужно отловить исключение из функции async.

try
{
    var a = await Foo1();
}
catch(MyException me)
{
    throw me;
}

Bar1(a);
Bar2(a);
Bar3(a);
try
{
    var b = await Foo2(a);
}
catch(MyException2 me2)
{
    throw me2;
}
Bar4(a,b);

Это определяет переменную внутри tryи теперь это ошибка компиляции.Если я хочу сохранить линейный код, тогда try должен охватывать слишком много строк, что, на мой взгляд, не очень хорошая практика?Теперь он создает пирамиду с отступом:

try
{
    var a = await Foo1();
    Bar1(a);
    Bar2(a);
    Bar3(a);
    try
    {
        var b = await Foo2(a);
        Bar4(a, b);
    }
    catch (MyException2 me2)
    {
        throw me2;
    }
}
catch (MyException me)
{
    throw me;
}

Таким образом, чтобы try только эту строку и поддерживать линейный код, мне нужно сначала переместить объявление в пустое состояние, а затем присвоить в try, чтобы сохранитьпеременная на внешней области видимости.

A a;
try
{
    a = await Foo1();
}
catch (MyException me)
{
    throw me;
}

Bar1(a);
Bar2(a);
Bar3(a);
B b;
try
{
    b = await Foo2(a);
}
catch (MyException2 me2)
{
    throw me2;
}
Bar4(a, b);

Теперь она вроде линейная.Я должен прекратить использовать var тоже, потому что я больше не могу сделать вывод из async метода.Я чувствую, что C # предоставил бы более элегантное решение, которое мне не хватает?

Ответы [ 4 ]

1 голос
/ 25 сентября 2019

Просто объявите переменную (без ее установки) перед try.

Кроме того, примерно catch me;:

  • Если весь ваш блок catch равен только собирается отбросить его, тогда у catch нет цели - просто не поймайте его вообще.Но я предполагаю, что вы, возможно, просто пропустили этот код.
  • Если вам нужно что-то сделать в catch (например, записать в журнал исключение), а затем сбросить его, используйте просто throw;.Это отбросит исключение без изменения трассировки стека - и это важно!
    • Если вы используете throw me;, трассировка стека покажет, что исключение произошло в throw me;.
    • Если вы используете только throw;, трассировка стека покажет, что исключение произошлона линии это на самом деле (await Foo1();, например).Это лучше ™.Здесь читается больше об этом здесь .

Вот все эти советы вместе взятые:

object a; //use the actual type
try
{
    a = await Foo1();
}
catch(MyException me)
{
    //do something else (or else just don't catch)
    throw;
}
Bar1(a);
Bar2(a);
Bar3(a);

object b; //use the actual type
try
{
    b = await Foo2(a);
}
catch(MyException2 me2)
{
    //do something else (or else just don't catch)
    throw;
}
Bar4(a,b);
1 голос
/ 25 сентября 2019

Я думаю, вы можете сделать что-то вроде

public struct Result<TResult>
{
    public static Result<TResult> Ok(TResult data) => new Result<TResult>(data, true);
    public static Result<TResult> Error() => new Result<TResult>(default(TResult), false);

    private Result(TResult data, bool success)
    {
        Data = data;
        Success = success;
    }

    public bool Success { get; }
    public TResult Data { get; }
}

public static class TaskExt
{
    public static async Task<Result<T>> AwaitSafe<T, TException>(this Task<T> task, Action<TException> handle)
        where TException : Exception
    {
        var result = Result<T>.Error();
        try
        {
            result = Result<T>.Ok(await task);
        }
        catch (TException ex)
        {
            handle.Invoke(ex);
        }

        return result;
    }
}

Использование:

 public async Task Exec()
    {
        var cli = new HttpClient();
        var result = await cli.GetStringAsync("https://google.com")
           .AwaitSafe((Exception ex) => throw ex);
        if (result.Success)
        {
            //good
        }
        else
        {
            //bad
        }
    }
0 голосов
/ 25 сентября 2019

Этот ответ вдохновлен ответом Мткаченко .Блоки try-catch можно избежать, используя метод Task.WhenAny для безопасного await Task, а затем запрашивая его свойство IsFaulted, чтобы определить, безопасно ли оночтобы получить доступ к Result.

//var a = await Foo1();
var task1 = await Task.WhenAny(Foo1());
if (task1.IsFaulted) return; // Or do something else
var a = task1.Result;

Bar1(a);
Bar2(a);
Bar3(a);

//var b = await Foo2(a);
var task2 = await Task.WhenAny(Foo2(a));
if (task2.IsFaulted) return; // Or do something else
var b = task2.Result;

Bar4(a, b);

Если вы считаете, что использование Task.WhenAny для безопасного ожидания противоречит цели этого метода, вы можете использовать вместо него метод расширения, указанный ниже:

public static async Task<Task<T>> AwaitSafe<T>(this Task<T> source)
{
    try
    {
        await source.ConfigureAwait(false);
    }
    catch { }
    return source;
}

Пример использования:

var task1 = await Foo1().AwaitSafe();
0 голосов
/ 25 сентября 2019

Почему бы не поместить весь код в try, для которого требуется переменная, объявленная внутри try?

try
{
    var a = await Foo1();
    Bar1(a);
    Bar2(a);
    Bar3(a);
}
catch(MyException me)
{
    // Use `me` or log the event
    throw me;
}

try
{
    var b = await Foo2(a);
    Bar4(a,b);
}
catch(MyException2 me2)
{
    // Use `me2` or log the event
    throw me2;
}
...