[英]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.Current
為null
或類型為System.Threading.SynchronizationContext
,則它將自動將同步上下文覆蓋到WindowsFormsSynchronizationContext
。
Control類使用WindowsFormsSynchronizationContext
覆蓋SynchronizationContext.Current
時,對Send
和Post
所有調用都期望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.