【问题标题】:What happens when you await a failed task等待失败的任务时会发生什么
【发布时间】:2014-11-22 13:50:33
【问题描述】:

我有一个理论问题要问你。如果我在另一个任务中等待一个任务的结果会发生什么?我想知道我当前的系统以后是否可以工作。

一个任务被启动并做一些事情。在某些时候,该任务可能需要另一个任务来处理当前任务本身无法处理的数据。所以我使用 await 来确保只要他没有辅助任务的结果,当前任务就不会继续。但是如果助手失败了怎么办?当前任务会保持锁定状态吗?

我能否以某种方式避免这种死锁(不改变系统本身 - 任务中的任务)?

【问题讨论】:

  • 写个简单的测试代码看看结果如何?
  • 你不能awaitResult,你可以等待任务。如果失败,会抛出异常
  • 当然我可以写测试代码,但这比在这里问要花更长的时间。回答这个问题不应该太多努力:)否则我很抱歉。对不起,我的错。当然,我的意思是等待任务。那么理论上,如果等待的任务失败,“等待”会抛出异常?

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


【解决方案1】:

这真的很容易测试。例如:

[TestMethod, ExpectedException(typeof(Exception))]
public async Task DoFaultedTaskThrowOnAwait()
{
  var task = Task.Factory.StartNew(() => { throw new Exception("Error 42"); });
  await task;
}

[TestMethod, ExpectedException(typeof(AggregateException))]
public void DoFaultedTaskThrowOnWait()
{
  var task = Task.Factory.StartNew(() => { throw new Exception("Error 42"); });
  task.Wait();
}

两个测试都通过了,注意Wait 抛出了AggregateExceptionawait 抛出了Exception

【讨论】:

    【解决方案2】:

    一个任务被启动并做一些事情。在某些时候,该任务可能需要另一个任务来处理当前任务本身无法处理的数据。所以我使用 await 来确保只要他没有辅助任务的结果,当前任务就不会继续。但是如果助手失败了怎么办?当前任务会保持锁定状态吗?

    asyncawait 背后的核心理念是异步代码的工作方式与同步代码几乎相同。

    所以,如果你有这样的同步代码:

    void HelperMethod()
    {
      throw new InvalidOperationException("test");
    }
    
    void DoStuff()
    {
      HelperMethod();
    }
    

    那么你会期望DoStuff 从辅助方法传播InvalidOperationException。同样,异步代码也会发生这种情况:

    async Task HelperMethodAsync()
    {
        throw new InvalidOperationException("test");
    }
    
    async Task DoStuffAsync()
    {
        await HelperMethodAsync();
    }
    

    也就是说,DoStuffAsync 也会传播 InvalidOperationException

    现在,它的工作方式完全当然不一样,因为它必须是异步的,但一般的想法是你所有的控制流,比如try/catchfor 循环等,所有异步代码都“正常工作”,与同步代码非常相似。

    实际上是这样的,当HelperMethodInvalidOperationException 结束时,异常被捕获并放在返回的Task 上,任务就完成了。当DoStuffAsync 中的await 看到任务已完成时,它会检查其异常并重新引发第一个异常(在这种情况下,只有一个,InvalidOperationException)。并且它以保留异常调用堆栈的方式重新引发它。这反过来会导致从DoStuffAsync 返回的Task 以同样的异常完成。

    所以,asyncawait 在幕后做了一些工作,以确保您可以使用 await 调用其他方法并使用 try/catch,就像您在同步代码。但大多数时候你不必意识到这一点。

    【讨论】:

    • 感谢您的精彩解释。与此相关的另一个问题:当我调用“task.Result”(或其他方式 task.Wait() )时,它真的被锁定了,不是吗?这是一个可能的死锁来源,还是安全的?
    • 多么巧合。我真的遇到了这种情况,这甚至不是我最初担心的问题。但仍然非常有用,因为这种情况现在恰好发生在我认为可以的位置:我已经异步运行的方法(该方法本身不是异步的,但它在第二个线程上运行)创建另一个任务并且必须等待它完成。同样的问题 :-/ 所以我真的必须重新设计我的整个系统才能拥有可以等待这些任务的异步方法......
    【解决方案3】:

    如果我在另一个任务中等待一个任务的结果会发生什么?

    如果它是 awaitable(意味着它具有 GetAwaiter 方法),则只能 awaitTask.Result。这种情况很少见。我假设您的意思是 await 在内部任务中。

    但是如果助手失败了怎么办?当前任务会保持锁定状态吗?

    首先,我不确定您所说的“锁定”是什么意思。当您在内部任务上await 时,该任务未锁定。控制权交还给调用方法,直到内部任务完成。如果该内部任务失败并且您未能正确处理该异常,则您的父任务也会出错。您需要确保优雅地处理异常:

    var task = 
      Task.Run(async () =>
               {
                     try
                     {
                            await AsyncHelper.DoSomethingAsync();
                     }
                     catch (Exception e)
                     {
                           // Handle exception gracefully
                     }
               });
    

    如果您在父任务上await,您会注意到从未处理的内部任务传播的内部异常。

    我能以某种方式避免这种僵局吗?

    我认为您的代码没有理由在这种特定情况下出现死锁。不知道为什么这会让你担心。

    【讨论】:

      猜你喜欢
      • 2012-09-11
      • 1970-01-01
      • 1970-01-01
      • 2019-04-29
      • 2016-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多