Try-Catch Асинхронные исключения - PullRequest
9 голосов
/ 16 июня 2011

Этот пример "fails":

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

То есть исключение с текстом "fail" всплывает.

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

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 могла быть запущена.

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

1 Ответ

5 голосов
/ 16 июня 2011

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

Поскольку main - это точка входа вашего приложения, она вызывается синхронно и, вероятно, какточка входа не запускает поведение переноса задач, поэтому await не запускается должным образом, и 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 в качестве внутреннего исключения.

...