【问题标题】:Throwing immediately from async method立即从异步方法中抛出
【发布时间】:2014-05-08 15:41:28
【问题描述】:

async Task 方法抛出的异常的 normal behavior 是保持休眠状态,直到它们稍后被观察到,或者直到任务被垃圾收集。

我能想到我可能想立即扔掉的情况。这是一个例子:

public static async Task TestExAsync(string filename)
{
    // the file is missing, but it may be there again
    // when the exception gets observed 5 seconds later,
    // hard to debug

    if (!System.IO.File.Exists(filename))
        throw new System.IO.FileNotFoundException(filename);

    await Task.Delay(1000);
}

public static void Main()
{
    var task = TestExAsync("filename");
    try
    {
        Thread.Sleep(5000); // do other work
        task.Wait(); // wait and observe
    }
    catch (AggregateException ex)
    {
        Console.WriteLine(new { ex.InnerException.Message, task.IsCanceled });
    }
    Console.ReadLine();
}

我可以使用async void 来解决这个问题,它会立即抛出:

// disable the "use await" warning
#pragma warning disable 1998
public static async void ThrowNow(Exception ex)
{
    throw ex;
}
#pragma warning restore 1998

public static async Task TestExAsync(string filename)
{
    if (!System.IO.File.Exists(filename))
        ThrowNow(new System.IO.FileNotFoundException(filename));

    await Task.Delay(1000);
}

现在我可以使用Dispatcher.UnhandledExceptionAppDomain.CurrentDomain.UnhandledException 立即处理此异常,至少可以立即引起用户注意。

对于这种情况还有其他选择吗?这可能是一个人为的问题吗?

【问题讨论】:

  • 我投票赞成一个人为的问题。 :) 理想情况下,异步代码将是响应式的,因此任务出错和观察到该任务出错之间的时间应该很短。
  • 投票确认,tks :)

标签: c# .net task-parallel-library async-await


【解决方案1】:

如果你真的想这样做,你可以使用同样的方法 Jon Skeet used in his reimplementation of LINQ:创建一个可以抛出或调用真正异步方法的同步方法:

public static Task TestExAsync(string filename)
{
    if (!System.IO.File.Exists(filename))
        throw new System.IO.FileNotFoundException(filename);

    return TestExAsyncImpl(filename);
}

private static async Task TestExAsyncImpl(string filename)
{
    await Task.Delay(1000);
}

请记住,我认为假设 Task 返回方法不会直接抛出是正常的。例如,在正常情况下可以使用Task.WhenAll()获取多个操作的所有异常,但是当立即抛出异常时,这种方法将不起作用。

【讨论】:

    【解决方案2】:

    我认为正常的行为是合适的。您的线程依赖async 函数的结果来进行下一次处理,因此应该在您的线程上抛出异常。 然后,您的线程代码可以采取适当的措施从异常中恢复。因为您可以传递您的任务并启动许多任务,所以您的恢复代码可能位于您需要获取任务结果的其他位置,而不是原始调用代码。如果立即抛出异常,它可能会被抛出外部您的恢复代码。

    asynch void 函数立即抛出,这是有道理的,因为没有任何东西取决于它的结果,也没有任务可以传递。

    顺便说一句,异常处理的重点是从异常中恢复应用程序状态,不应捕获任何您无法恢复的异常。当抛出异常时,您的应用程序状态可能已损坏,尝试继续使用已损坏的应用程序会导致更多问题和安全漏洞。

    【讨论】:

    • "... 不应捕获任何您无法恢复的异常" - 这正是我对丢失文件示例的观点。我宁愿让用户在那里终止应用程序,而不是在 5 秒后(或者永远不会,如果我的代码中存在错误并且任务从未被观察到)。否则我同意,但我想让这个问题再拖一会儿,看看其他人会怎么想。
    • @Noseratio:如果应用程序可以从异常中恢复,我们不应该终止它。这就是我的观点。当其他代码使用您的代码时,其他代码可以从异常中恢复。
    • 我认为我的示例并非如此。如果其他代码(在未来观察到异常)将尝试恢复,则可能存在一个 不同 文件,而不是丢失的文件。这可能会导致数据丢失。查看 Eric Lippert 的"Vexing exceptions"
    • 从异常中恢复并不一定意味着重试。它可以意味着任何东西,在我们的例子中,它可能意味着中止操作并向用户指示错误(例如显示对话框、将错误写入控制台/日志文件/注册表等)。不应仅仅因为文件丢失而使应用程序崩溃。
    • @Mike Rosoft:这就是我所说的恢复。恢复代码中可能存在的另一个逻辑是回滚损坏的状态
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    • 1970-01-01
    • 2016-07-10
    • 2013-01-01
    相关资源
    最近更新 更多