【问题标题】:Task doesn't Go to Faulted State If ConfigurAwait set to False如果 ConfigurAwait 设置为 False,任务不会进入故障状态
【发布时间】:2017-02-11 18:23:28
【问题描述】:

这就是我想要实现的目标。我启动一项任务并且不等待/结果。为了确保启动的任务进入故障状态(例如抛出异常),我通过在 Continuation 中调用 Environment FailFast 来使进程崩溃。

我面临的问题是,如果我在代码下方运行,在 ContinueWith 内部,任务的状态(引发异常)显示为“RanToCompletion”。我预计它是故障状态。

    private Task KickOfTaskWorkAsync()
    {
        var createdTask = Task.Run(() => this.RunTestTaskAsync(CancellationToken.None).ConfigureAwait(false), CancellationToken.None);

        createdTask.ContinueWith(
            task => Console.WriteLine("Task State In Continue with => {0}", task.Status));

        return createdTask;
    }

    private async Task RunTestTaskAsync(CancellationToken cancellationToken)
    {
        throw new Exception("CrashingRoutine: Crashing by Design");
    }

这真的很奇怪 :( 如果我删除 Task.Run 函数调用中的“ConfigureAwait(false)”,任务确实会在 Continue 中进入故障状态。真的无法解释发生了什么,希望能得到一些帮助来自社区。​​p>

[更新]:我的同事指出了一个明显的错误。当我在 Test.Run 中调用 RunTestAsync 时,我正在使用 ConfigureAwait,即使我没有等待它。在这种情况下,ConfigureAwait 不会将任务返回给 Task.Run。如果我不调用 ConfigureAwait,则确实会返回一个 Task,并且事情会按预期工作。

【问题讨论】:

  • 您使用ContinueWith 有什么原因吗? ContinueWith 用于预异步等待时代;现在您可以使用await RunTestTaskAsync() 后跟Console.WriteLine() 并以更标准的方式捕获您想要的任何异常;即try..catch.
  • @sellotape:await 现在是处理延续的更惯用的方式是正确的。但是,即使使用await,OP 仍然会遇到同样的问题。他们将等待错误的Task 对象,但仍会看到处于非故障状态的任务。
  • @PeterDuniho - 如果他要等待createdTask,那么可以,但如果他只是等待RunTestTaskAsync()(似乎没有理由不这样做,但我想没有足够的背景可以确定) catch 将捕获他抛出的异常。
  • @sellotape: "如果他只是在等待 RunTestTaskAsync()"——但他不是。而他不是他的问题的全部症结所在。我的意思是,只是告诉他使用await 根本不能解决他的问题。如果这里ContinueWith() 被await 替换,同样的问题仍然会发生。这个问题只是稍微改写了代码。
  • @PeterDuniho - 我并不是说这是一个答案;而只是OP可能会发现有用的相关信息,这就是为什么我发表评论而不是答案(无论如何你的答案涵盖了这个问题)。我还在第一条评论中特别提到了await RunTestTaskAsync()。

标签: c# asynchronous parallel-processing task configureawait


【解决方案1】:

您的错误是更广泛的错误类别的一个具体示例:您没有观察到您真正关心的Task。

在您的代码示例中,RunTestTaskAsync() 返回一个 Task 对象。它是同步完成的(因为没有await),所以它返回的Task 对象在方法返回时由于异常已经出错了。然后,您的代码会在这个错误的 Task 对象上调用 ConfigureAwait()。

但所有这些都发生在 另一个 Task 内部,即您在调用 Task.Run() 时开始的那个。这个Task 没有做任何事情来观察异常,所以它正常完成。

删除ConfigureAwait() 调用时观察到异常的原因与调用本身无关。如果您离开通话并转而通过true,您仍然无法观察到异常。删除调用时可以观察到异常的原因是,在没有调用ConfigureAwait() 的情况下,lambda 表达式的返回值为Task,而这调用了a different overload of Task.Run()。

这个重载与其他重载有点不同。来自文档:

将指定的工作排入队列以在线程池上运行,并为函数返回的任务返回一个代理。

也就是说,虽然它仍然启动一个新的Task,但它返回的Task 对象代表的不是Task,而是你的lambda 表达式返回的那个。该代理的状态与其包裹的 Task 相同,因此您可以看到它处于 Faulted 状态。

根据您发布的代码,我想说您首先不应该调用Task.Run()。以下内容也可以正常工作,而无需代理的开销和复杂性:

static void Main(string[] args)
{
    Task createdTask = RunTestTaskAsync();

    createdTask.ConfigureAwait(false);

    createdTask.ContinueWith(
        task => Console.WriteLine("Task State In Continue with => {0}", task.Status)).Wait();
}

private static async Task RunTestTaskAsync()
{
    throw new Exception("CrashingRoutine: Crashing by Design");
}

(我删除了CancellationToken 值,因为它们与您的问题完全无关,在这里完全是多余的。)

【讨论】:

  • 在没有相应的await 的情况下配置await(即createdTask.ConfigureAwait(false))有什么意义?再仔细观察,代码似乎永远不会运行(在那之前会抛出异常)。
  • @ta.speot.is:无论是通过ContinueWith() 还是await 语句,调用ConfigureAwait() 都会影响延续。而且您对异常也有误解。在线程中没有观察到,因为它被方法返回的Task 封装了。如果您费心运行代码,您就会知道这一点。有趣的是,人们在没有真正了解他们在看什么的情况下投反对票。
  • RE: #1 我的印象是ConfigureAwait 返回一个ConfiguredTaskAwaitable 并且那是 保存配置的东西。你是对的 RE:#2。
  • @ta.speot.is:是的,没错。您必须将调度程序传递给 ContinueWith()。 awaitable 被丢弃并且没有任何效果。尽管如此,关键是答案是正确的,并且编写代码只是为了尽可能地匹配原始问题的代码,同时提供符合 OP 期望的行为。几乎不值得投反对票,仅仅因为原始代码不完善。
猜你喜欢
  • 2010-09-23
  • 1970-01-01
  • 1970-01-01
  • 2018-02-07
  • 2015-07-06
  • 2021-04-14
  • 1970-01-01
  • 2020-01-29
  • 2012-01-28
相关资源
最近更新 更多