Попробуйте поймать асинхронные исключения

Этот пример "не работает":

static async void Main(string[] args)
{
    try
    {
        await TaskEx.Run(() => { throw new Exception("failure"); });
    }
    catch (Exception)
    {
        throw new Exception("success");
    }
}

То есть всплывает исключение с текстом «сбой».

Затем я попробовал этот обходной путь:

static async void Main(string[] args)
{
    try
    {
        await SafeRun(() => { throw new Exception("failure"); });
    }
    catch (Exception)
    {
        throw new Exception("success");
    }
}

static async Task SafeRun(Action action)
{
    var ex = default(Exception);
    await TaskEx.Run(() =>
    {
        try
        {
            action();
        }
        catch (Exception _)
        {
            ex = _;
        }
    });
    if (ex != default(Exception))
        throw ex;
}

Это тоже не помогло.

Я предполагаю, что моя установка обновления Async CTP может быть отключена.

Должен ли этот код работать так, как я ожидаю («успех» всплывает, а не «неудача»), или он «не должен» работать таким образом. А если нет, то как бы вы обошли это?


person Bent Rasmussen    schedule 16.06.2011    source источник


Ответы (1)


Поведение, которое вы видите, скорее всего, является ошибкой краевого случая или даже может быть правильным, если оно не интуитивно понятно. Обычно, когда вы вызываете асинхронный метод синхронно, он оборачивает задачу для выполнения, и, поскольку никто не ждет завершения задачи, исключение никогда не попадает в основной поток. Если бы вы вызывали Main напрямую, это было бы успешно, но тогда ваша среда выполнения увидит исключение «успех» в другом потоке.

Поскольку main является точкой входа вашего приложения, она вызывается синхронно и, вероятно, поскольку точка входа не запускает поведение переноса задачи, поэтому ожидание не выполняется должным образом, а TaskEx.Run вызывает свой собственный поток, который отображается в среда выполнения как исключение, выбрасываемое в другом потоке.

Если бы вы запускали main как метод async, т.е. возвращая Task (поскольку async, который возвращает void, может быть действительно вызван только через await) и блокируя его из вашего синхронного основного контекста, вы получили бы соответствующее поведение, как показано ниже. иллюстрирует:

static async Task Main() {
    try {
        await TaskEx.Run(() => { throw new Exception("failure"); });
    } catch(Exception) {
        throw new Exception("success");
    }
}

static async Task Main2() {
    await Main();
}

[Test]
public void CallViaAwait() {
    var t = Main2();
    try {
        t.Wait();
        Assert.Fail("didn't throw");
    } catch(AggregateException e) {
        Assert.AreEqual("success",e.InnerException.Message);
    }
    }


[Test]
public void CallDirectly() {
    var t = Main();
    try {
        t.Wait();
        Assert.Fail("didn't throw");
    } catch(AggregateException e) {
        Assert.AreEqual("success", e.InnerException.Message);
    }
}

т.е. Ошибка задачи с AggregateException, которая содержит исключение success как внутреннее исключение.

person Arne Claassen    schedule 16.06.2011
comment
Это из консольного приложения для тестирования асинхронности, поэтому в моем случае метод верхнего уровня является основным методом. - person Bent Rasmussen; 17.06.2011
comment
@Bent, я обновил ответ, чтобы отразить, как вы звоните, что может быть или не быть ошибкой, но вызвано тем, что async точка входа не определена. - person Arne Claassen; 18.06.2011