2017-03-22 69 views
0

目的为什么这个异步单元测试永远阻塞线程?

我想用Task<T>自定义扩展方法来编写C#中的异步工作流程。我想单元测试每个我将用来确保它们正常工作的基本扩展方法,因此当我开始将它们组合在一起时,行为不会有任何意外。这种测试最重要的方面之一就是确保这些方法在他们应该执行任务时运行,而不是在工作流程正在编写时立即执行任务,而只在等待组成工作流程时执行。

实际上,我的扩展方法似乎工作正常。然而,我还没有能够创建一个单元测试来测试这些方法的等待方面而不阻塞测试线程。

我使用Visual Studio 2015年,C#5,.NET 4.5.1,和NUnit 3.

代码

这里是一个这样的扩展方法,我想测试:

public static async Task<T> Let<T>(this Task<T> source, Action<T> action) 
{ 
    var result = await source; 
    action(result); 
    return result; 
} 

以下是我为该方法所做的测试。 (这个项目使用Shouldly它提供了NUnit的一个流畅的界面,所以你可以写x.ShouldBe(3)意味着Assert.AreEqual(3, x)。)

[TestFixture, Category("Task")] 
public class WhenLettingTask { 

    private static bool sourceExecuted; 
    private static int sideEffectResult; 

    private static Task<int> Source => 
     new Task<int>(() => { 
      sourceExecuted = true; 
      return 1; 
     }); 

    private static readonly Action<int> SideEffect = 
     n => { sideEffectResult = n; }; 

    [SetUp] 
    public void TestSetup() { 
     sourceExecuted = false; 
     sideEffectResult = 0; 
    } 


    [Test] 
    public void ShouldNotExecuteAnythingImmediately() { 
     var composed = Source.Let(SideEffect); 

     sourceExecuted.ShouldBeFalse(); 
     sideEffectResult.ShouldBe(0); 
    } 

    [Test] 
    public async Task ReturnedTaskShouldExecuteInputsOnlyOnce() { 
     var composed = Source.Let(SideEffect); 

     var result = await composed; //Blocks forever 
     sourceExecuted.ShouldBeTrue(); 
     sideEffectResult.ShouldBe(1); 

     sourceExecuted = false; 
     sideEffectResult = 0; 

     for (var i = 0; i < 10; i++) { 
      result = await composed; 

      sourceExecuted.ShouldBeFalse(); 
      sideEffectResult.ShouldBe(0); 
     } 
    } 
} 

第一个测试工作正常,但在第一await第二个块,直到永远。

研究

有趣的是,如果删除await被测方法内,该测试将不阻塞。

public static async Task<T> Let<T>(this Task<T> source, Action<T> action) 
{ 
    T result = default(T); 
    action(result); 
    return result; 
} 

在寻求帮助有关异步测试,我已经看到了很多帖子推荐使用的Task.FromResult获得异步测试的工作,但是这基本上是短路的等待方面,因为Task<T>创建这种方式始于RanToCompletion的状态,并且永远不需要等待。

基于斯科特张伯伦答案解析 ,我改变了Source财产在我的测试夹具此:

private static Task<int> Source => 
    Task.Run<int>(() => { 
     Thread.Sleep(1000); 
     sourceExecuted = true; 
     return 1; 
    }); 

Task.Run将立即启动任务,这使我的第二次测试通过。需要Thread.Sleep以便第一次测试仍然通过。

回答

4

你从未开始任务,你有什么被称为“冷的任务”。等待不开始任务,只等待他们完成。

除非您正在编写任务计划程序,否则不应该调用new Task (,而应使用Task.Run (来创建一个一旦函数返回就已处于运行状态的热任务。

相关问题