【问题标题】:Does Task.RunSynchronously() work "recursively"?Task.RunSynchronously() 是否“递归”工作?
【发布时间】:2013-04-18 04:06:43
【问题描述】:

如果我在 C# 中对 Task 调用 RunSynchronously(),这是否会导致异步调用进一步深入兔子洞以同步运行?

假设我有一个名为UpdateAsync() 的方法。在此方法中,对DoSomethingAsync() 进行了另一个异步调用,在此内部我们再次找到DoSomethingElseAsync(),将在'UpdateAsync()' 上调用RunSynchronously() 导致RunSynchronously() 也间接在DoSomethingAsync() 上被调用?

我的问题的原因: 我有一种情况,我“需要”在 catch-block 中调用异步方法 (UpdateAsync()),并想知道用 RunSynchronously() 调用它是否安全。文档非常清楚地说明您不能在catch-block 中使用await。 (严格来说,我可以在 catch-block 中使用布尔值并在 try-catch 之后调用 UpdateAsync(),但这感觉很脏)。对双重问题感到抱歉,但正如您可能理解的那样,我不太知道如何表达它并且对这个领域没有很好的理解。

(编辑: 我不知道如何找出方法是否被异步调用。您将如何为此编写单元测试?是否有可能以某种方式记录它?)

【问题讨论】:

  • 如果您使用async/await 并调用async 方法,您很可能不想通过调用RunSynchronously() 来阻止 - 所以我会选择布尔标志选项。
  • @NicholasButler 使用这样的 bool 标志感觉就像是一种黑客行为,但在某种程度上,异步调用异步方法确实更安全。无论如何,这在我们的应用程序中只发生在一个地方。

标签: c# asynchronous .net-4.0 async-await


【解决方案1】:

我“需要”调用一个异步方法并想知道用RunSynchronously() 调用它是否安全

有两种Tasks:基于代码的* Tasks(您可以使用Task 构造函数或Task.Factory.StartNew() 创建它们)和promise-style Tasks(您可以创建它们手动使用TaskCompletionSource 或编写async 方法)。

您可以通过调用Start()RunSynchronously() 开始的唯一Tasks 是未启动的基于代码的Tasks。由于异步方法返回 promise 样式的 Tasks(或者可能已经开始基于代码的 Tasks),因此在它们上调用 RunSynchronously() 无效并且会导致异常。

所以,要真正回答你的问题:你所问的是不可能的,所以问它是否安全是没有意义的。


* 这不是正式名称,不知道有没有。

【讨论】:

  • +1 谢谢。这是复杂的东西。我要选择布尔标志解决方案。
  • @svick,异步方法返回的Task或者Task不是自启动的,所以可以使用Task或者Task在非运行状态下获取 构造函数。我自己可能会感到困惑,但我看到了一些有效性,并试图在午休时间“建模”它 - 请参阅下面的答案。
【解决方案2】:

如果没有代码,很难预测它将如何执行嵌套的异步方法。

您可以登录每个异步方法 Thread.CurrentThread.ManagedThreadId 属性并将 id 与您拥有的其他线程 ID 进行比较。当它们发生变化时,您的异步方法将处于多线程执行中

或者尝试使用 Visual Studio 中的 Concurrency Visualizer,Analyze 菜单。使用 Task 类实例,即使使用 C#5 异步语法,也无法知道您是在并行执行还是在另一个线程执行。

【讨论】:

  • 感谢有关记录线程 ID 的建议,我会尝试的。关于它很难预测,这是否意味着 RunSynchronously() 中没有任何继承可以保证代码中进一步发生的事情将同步运行?
  • Avout RunSynchronously 我还没有找到任何保证嵌套异步方法将如何执行。毫无疑问,我可以说 RunSynchronously 保证当前异步方法或任务将在同一个线程中执行,从那里调用它。
【解决方案3】:

我想我会与@svick 的回答相矛盾,但我觉得 OP 问题是有效的,因为异步方法的“承诺”(使用 Svick 的术语)可以获得任务,但不能启动,因此允许执行 @ 987654321@.

    static void Main(string[] args)
    {
        ...
        //obtain a Task that's not started
        var t = new Task<int>((ob) => GetIntAsync((string)ob).Result, someString);
        ...
    }

    static async Task<int> GetIntAsync(string callerThreadId)
    {
       ...

话虽如此,答案是:不,RunSynchronously() 会影响您运行它的任务。如果调用链稍后包含更多异步调用,它们将异步运行。

我有一个小控制台应用程序正在对此进行建模,有人有兴趣看到它,但这个概念很容易重现 - 只需链接足够多的异步调用并在同步和异步运行最早的调用之间切换以查看不同行为。

【讨论】:

  • 您如何从async 方法获得Task,该方法可以是Start()ed?也许您对以下事实感到困惑:promise TaskState 在完成之前是 WaitingForActivation
  • 进行了显示代码的编辑。你在另一个线程上表示这不是同一个任务 - 你可能是对的 - 它从我的脑海中掠过,但出于 OP 问题的意图和目的,它是等价的,所以我把这个想法抛在脑后。
  • @G.Stoynev 这不会使async 方法的Task 处于未启动状态。这是创建一个新任务,处于未启动状态,启动时调用async 方法。这与直接从异步方法获取未启动的Task 不同。
  • 我也这么认为,但这就是您完成原始问题的方式。
  • @G.Stoynev 好吧,您可以通过某种方式使用RunSynchronously(),但它一点用处都没有。基本上,您所建议的只是一种复杂的方式来做与直接调用 GetIntAsync() 相同的事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-19
  • 2013-03-20
  • 2012-10-04
  • 2018-04-24
  • 2013-03-09
  • 2011-01-25
相关资源
最近更新 更多