簡體   English   中英

為什么Task.ContinueWith無法在此單元測試中執行?

[英]Why does the Task.ContinueWith fail to execute in this Unit Test?

我遇到了單元測試失敗的問題,因為TPL Task從未執行過它的ContinueWith(x, TaskScheduler.FromCurrentSynchronizationContext())

原來是因為在啟動任務之前意外創建了Winforms UI控件。

這是復制它的示例。 您將看到,如果按原樣運行測試,則測試通過。 如果您在未注釋表單行的情況下運行測試,則它將失敗。

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Create new sync context for unit test
        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());

        var waitHandle = new ManualResetEvent(false);

        var doer = new DoSomethinger();

        //Uncommenting this line causes the ContinueWith part of the Task
        //below never to execute.
        //var f = new Form();

        doer.DoSomethingAsync(() => waitHandle.Set());

        Assert.IsTrue(waitHandle.WaitOne(10000), "Wait timeout exceeded.");
    }
}


public class DoSomethinger
{
    public void DoSomethingAsync(Action onCompleted)
    {
        var task = Task.Factory.StartNew(() => Thread.Sleep(1000));

        task.ContinueWith(t =>
        {
            if (onCompleted != null)
                onCompleted();

        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
}

誰能解釋為什么會這樣?

我以為可能是因為使用了錯誤的 SynchronizationContext ,但實際上, ContinueWith 根本不會執行! 此外,在此單元測試中,是否正確的SynchronizationContext是無關緊要的,因為只要在任何線程上調用waitHandle.set() ,測試都將通過。

我忽略了代碼中的注釋部分,實際上,當取消注釋var f = new Form();時,該操作將失敗var f = new Form();

原因很微妙,如果Control類看到SynchronizationContext.Currentnull或類型為System.Threading.SynchronizationContext ,則它將自動將同步上下文覆蓋到WindowsFormsSynchronizationContext

Control類使用WindowsFormsSynchronizationContext覆蓋SynchronizationContext.Current時,對SendPost所有調用都期望Windows消息循環正在運行以便正常工作。 在創建Handle並運行消息循環之前,這不會發生。

有問題的代碼的相關部分:

internal Control(bool autoInstallSyncContext)
{
    ...
    if (autoInstallSyncContext)
    {
       //This overwrites your SynchronizationContext
        WindowsFormsSynchronizationContext.InstallIfNeeded();
    }
}

您可以在此處引用WindowsFormsSynchronizationContext.InstallIfNeeded的源。

如果要覆蓋SynchronizationContext ,則需要自定義實現SynchronizationContext才能使它起作用。

解決方法:

internal class MyContext : SynchronizationContext
{

}

[TestMethod]
public void TestMethod1()
{
    // Create new sync context for unit test
    SynchronizationContext.SetSynchronizationContext(new MyContext());

    var waitHandle = new ManualResetEvent(false);

    var doer = new DoSomethinger();
    var f = new Form();

    doer.DoSomethingAsync(() => waitHandle.Set());

    Assert.IsTrue(waitHandle.WaitOne(10000), "Wait timeout exceeded.");
}

上面的代碼按預期工作:)

或者,您可以將WindowsFormsSynchronizationContext.AutoInstall設置為false ,這將防止上述同步上下文的自動覆蓋。(感謝OP @OffHeGoes在評論中提到了這一點)

注釋掉該行后,您的SynchronizationContext是您創建的默認行。 這將導致TaskScheduler.FromCurrentSynchrozisationContext()使用默認的調度程序,該調度程序將在線程池上運行延續。

一旦創建了類似於Form的Winforms對象,當前的SynchronizationContext就會變成WindowsFormsSynchronizationContext ,這將返回一個依賴於WinForms消息泵的調度程序來調度繼續。

由於在單元測試中沒有WinForms泵,所以該延續永遠不會運行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM