【问题标题】:Is it possible to test if a method is running with a captured SynchronizationContext when started by a Task?是否可以测试方法在由任务启动时是否使用捕获的 SynchronizationContext 运行?
【发布时间】:2019-02-27 16:09:04
【问题描述】:

我可能在某个地方遗漏了答案,或者这是一些微不足道的问题,但我还没有找到。

这实际上是我试图在代码中实现的目标:

public static async Task CapturesContext()
{
    await Task.Run(() => DoWhatever());
}

public static async Task DoesNotCaptureContext()
{
    await Task.Run(() => DoWhatever()).ConfigureAwait(false);
}

public static void DoWhatever()
{
    //Any way to test here that it was run with/without a captured SynchronizationContext?
}

上面是一个非常简单的例子,但表达了我想要完成的事情。

我们的目标是在非常大的代码库中清除 ConfigureAwait 的不当用法。

给定使用Task 运行的任何方法,是否可以通过Assert 或单元测试之类的代码检查给定方法是否使用捕获的SynchronizationContext 运行?

如果没有,是否有其他方法可以实现我的目标?

【问题讨论】:

  • 我能想象到的不正确使用ConfigureAwait(false)的唯一情况是当您需要更新UI上的某些内容时,否则我认为ConfigureAwait(false)是避免不必要地更改线程的好习惯,还要考虑控制台应用程序和 Net Core(可能还有其他一些)没有SynchronizationContext
  • @Unnamed 我没有使用 .NET Core(更新了标签)。很多时候我不想使用ConfigureAwait(false),例如在 ASP.NET、WinForms、WPF 线程中以及当我需要当前文化进行本地化时。除其他原因外。

标签: c# task synchronizationcontext .net-4.7.2


【解决方案1】:

目标是在一个非常大的代码库中清除不正确的 ConfigureAwait 用法。

一些团队为此选择使用代码分析工具。有几种可用。我见过的最常见的方法是要求为每个await 提供一个ConfigureAwait,并明确指定truefalse。这可以确保每个await 都经过审查并且上下文流是明确的。其他团队应用“始终使用ConfigureAwait(false)”的项目特定规则,并且只依赖于对无法遵循该规则的项目进行代码审查。

您的示例代码的问题在于,DoWhatever不可能知道它是否被间接调用,因为 Task.Run。如果你重写这些方法,这就变得很清楚了:

public static async Task CapturesContext()
{
  var task = Task.Run(() => DoWhatever());
  await task;
}

public static async Task DoesNotCaptureContext()
{
  var task = Task.Run(() => DoWhatever());
  var configuredTask = task.ConfigureAwait(false);
  await configuredTask;
}

重写方法的第一行应该清楚地表明DoWhatever 不知道CapturesContextDoesNotCaptureContext 是否会捕获上下文。注意“will”(将来时)——DoWhatever 完全有可能在 ConfigureAwait(false) 被调用之前运行并完成执行

现在,您可以在任务中检查它是否正在现在上下文中运行。但在这种情况下,对于这两个示例方法,DoWhatever 将不会看到上下文,因为Task.Run。所以这并不能帮助您检测到CapturesContext 确实捕获了上下文的事实; DoWhatever 看不到上下文,因此无法检测到它。

自定义SynchronizationContext 是单元测试的一个很好的解决方案,但是在运行时使用它会很尴尬,因为你确实有一些需要上下文的方法。出于这个原因,大多数团队选择依赖代码审查和/或代码分析工具。

【讨论】:

    【解决方案2】:

    创建一个SynchronizationContext,它在Post 和Send 的实现中引发异常。或者,让它设置一个布尔值,指示是否调用了 SendPost,以便您稍后检查该布尔值(如果您这样做,您可能希望运行提供的委托,否则您可能会面临死锁的风险)。

    每当测试一个永远不应使用当前同步上下文的方法时,在测试开始时将该自定义同步上下文的一个实例设置为当前同步上下文。

    【讨论】:

    • +1 这适用于单元测试,这很有帮助。任何方式我都可以在不使用自定义SynchronizationContext 的情况下进行编译时(甚至运行时抛出异常)检查?
    • @Zer0 任何编译时检查都只是猜测。您将永远无法知道是否有任何东西可以使用当前同步上下文,因为从代码分析工具和流诊断的角度来看,没有足够的信息来确定这一点。在一般情况下,需要做的是可证明无法解决的问题,因此您有一些误报和/或误报。如此简单地编写自定义同步上下文是如此快速和容易,我不明白您为什么要尝试避免它。如果可能的话,避免它会做更多的工作。
    • 是的,我怀疑编译时间是可能的,但也许是运行时间?我要说的是,是否有办法在没有单元测试的情况下进行检查。我同意这个解决方案很简单并且符合要求,假设适用的单元测试已经到位。如果某些东西没有经过单元测试,我想知道我是否可以在方法本身内“快速失败”。
    • @Zer0 当然。您不需要在单元测试中执行此操作。您可以在单元测试之外设置同步上下文。大概在这种情况下,您只需将其设置为默认上下文,而不是抛出的上下文。如果你想这样做,你甚至不需要避免在方法中捕获上下文。如果您这样做,请不要忘记在将控制权返回给调用者时将原始上下文设置回来。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-01
    • 2016-02-05
    相关资源
    最近更新 更多