【问题标题】:How do I write a test over a background service that has Task.Yield?如何在具有 Task.Yield 的后台服务上编写测试?
【发布时间】:2020-11-25 06:44:28
【问题描述】:

我有以下后台服务:

    public class MyBackgroundService : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // Required for the method to be executed asynchronously, allowing startup to continue.
            await Task.Yield();
            while (!stoppingToken.IsCancellationRequested)
            {
                DoSomeWork();
            }
    }

但是,我正在努力测试这一点;我不能等待ExecuteAsync 的完成,因为Task.Yield() 只是返回到当前上下文。所以在我的测试中,await service.ExecuteAsync(token) 立即返回。

如何在不使用Thread.Sleep 的情况下进行测试?

【问题讨论】:

  • 一些测试框架也支持异步测试,这意味着您可以在测试代码中等待您想要测试的异步方法。由于您使用的是 xUnit,请确保您使用的不是真正旧版本的 xUnit,您应该很好。然后基本上你还需要创建你的测试方法async Task,然后等待对 ExecuteAsync 的调用,xUnit 应该负责其余的工作。
  • 虽然ExecuteAsync 立即返回,但如果您真的在等待它返回 的任务,那么等待操作不应立即返回——除非stoppingToken 被取消。请提供minimal reproducible example
  • 相当有争议的设计。如果你想在线程池线程上运行一些代码 - 为什么不直接使用 Task.Run 呢?或者在这种情况下甚至更好地创建单独的线程(或使用LongRunning 标志启动任务),因为您似乎正在执行一些长时间的操作,而线程池并未设计。

标签: c# asp.net-core async-await xunit


【解决方案1】:

根据the docs

你可以使用 await Task.Yield();在异步方法中强制方法异步完成。如果存在当前同步上下文(SynchronizationContext 对象),这会将方法执行的其余部分发送回该上下文。但是,上下文将决定如何相对于可能未决的其他工作来确定这项工作的优先级。大多数 UI 环境中的 UI 线程上存在的同步上下文通常会将发布到上下文的工作优先于输入和渲染工作。因此,不要依赖 await Task.Yield();保持 UI 响应。有关详细信息,请参阅 .NET 并行编程博客中的 Useful Abstractions Enabled with ContinueWith 条目。

ASP.NET(默认情况下)没有同步上下文,但Task.Yield 可能仍然有用:

static async Task Main()
{
    var t1 = DoSomethingLengthyWithYieldAsync();
    Console.WriteLine("Before await t1");
    await t1;
    Console.WriteLine("After await t1");
    
    Console.WriteLine("----------");

    var t2 = DoSomethingLengthyWithoutYieldAsync();
    Console.WriteLine("Before await t2");
    await t2;
    Console.WriteLine("After await t2");
}

async Task DoSomethingLengthyWithYieldAsync()
{
    Console.WriteLine("Before Task.Yield");
    await Task.Yield();
    Console.WriteLine("Before Thread.Sleep");
    Thread.Sleep(1);
    Console.WriteLine("After Thread.Sleep");
}

async Task DoSomethingLengthyWithoutYieldAsync()
{
    Console.WriteLine("Before Thread.Sleep");
    Thread.Sleep(1);
    Console.WriteLine("After Thread.Sleep");
}

使用Task.Yield,只要达到await Task.Yield(),您就可以从异步方法调用中获得Task

要就您的特定用例提供更多建议,我们需要查看DoSomeWork() 的实现。

【讨论】:

    【解决方案2】:

    我不能等待 ExecuteAsync 完成,因为 Task.Yield() 只是返回到当前上下文。

    我假设你的意思是你不能awaitStartAsync 返回的任务,因为ExecuteAsyncprotected

    如果您想像这样对您的服务进行单元测试,我建议您将逻辑拆分为单独的可测试类型。 BackgroundServiceExecuteAsync 任务“隐藏”为私有成员。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-12
      • 1970-01-01
      • 2010-10-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多