2017-03-22 103 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 (來創建一個一旦函數返回就已處於運行狀態的熱任務。

相關問題