簡體   English   中英

在創建Form之前,帶有Application循環的NUnit測試會掛起

[英]NUnit test with Application loop in it hangs when Form is created before it

我對WebBrowser控件進行了一些測試,並用MessageLoopWorker包裝,如下所述: 新線程中的WebBrowser控件

但是,當另一個測試創建用戶控件或表單時,該測試將凍結並且永遠不會完成:

    [Test]
    public async Task WorksFine()
    {
        await MessageLoopWorker.Run(async () => new {});
    }

    [Test]
    public async Task NeverCompletes()
    {
        using (new Form()) ;
        await MessageLoopWorker.Run(async () => new {});
    }

    // a helper class to start the message loop and execute an asynchronous task
    public static class MessageLoopWorker
    {
        public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
        {
            var tcs = new TaskCompletionSource<object>();

            var thread = new Thread(() =>
            {
                EventHandler idleHandler = null;

                idleHandler = async (s, e) =>
                {
                    // handle Application.Idle just once
                    Application.Idle -= idleHandler;

                    // return to the message loop
                    await Task.Yield();

                    // and continue asynchronously
                    // propogate the result or exception
                    try
                    {
                        var result = await worker(args);
                        tcs.SetResult(result);
                    }
                    catch (Exception ex)
                    {
                        tcs.SetException(ex);
                    }

                    // signal to exit the message loop
                    // Application.Run will exit at this point
                    Application.ExitThread();
                };

                // handle Application.Idle just once
                // to make sure we're inside the message loop
                // and SynchronizationContext has been correctly installed
                Application.Idle += idleHandler;
                Application.Run();
            });

            // set STA model for the new thread
            thread.SetApartmentState(ApartmentState.STA);

            // start the thread and await for the task
            thread.Start();
            try
            {
                return await tcs.Task;
            }
            finally
            {
                thread.Join();
            }
        }
    }

代碼return await tcs.Task;很好,除了return await tcs.Task; 永遠不會回來。

new Form包裝到MessageLoopWorker.Run(...)中似乎使它更好,但是不幸的是,它不適用於更復雜的代碼。 而且,我還有很多其他關於表單和用戶控件的測試,希望避免將它們包裝到messageloopworker中。

也許可以固定MessageLoopWorker以避免與其他測試產生干擾?

更新:按照@Noseratio給出的令人驚奇的答案,我已經在MessageLoopWorker.Run調用之前重置了同步上下文,現在它運行良好。

更有意義的代碼:

[Test]
public async Task BasicControlTests()
{
  var form = new CustomForm();
  form.Method1();
  Assert....
}

[Test]
public async Task BasicControlTests()
{
    var form = new CustomForm();
    form.Method1();
    Assert....
}

[Test]
public async Task WebBrowserExtensionTest()
{
    SynchronizationContext.SetSynchronizationContext(null);

    await MessageLoopWorker.Run(async () => {
        var browser = new WebBrowser();
        // subscribe on browser's events
        // do something with browser
        // assert the event order
    });
}

如果運行測試時沒有使同步上下文無效,則WebBrowserExtensionTest在遵循BasicControlTests時會阻塞。 使用null可以順利通過。

這樣保留它可以嗎?

我在MSTest下對此進行了重現,但我相信以下所有內容同樣適用於NUnit。

首先,我知道該代碼可能已脫離上下文,但就目前而言,它似乎不是很有用。 為什么要在NeverCompletes內創建一個窗體,該窗體運行在與MessageLoopWorker生成的線程不同的隨機MSTest / NUnit線程上?

無論如何,您陷入了僵局,因為using (new Form())在該原始單元測試線程上安裝WindowsFormsSynchronizationContext的實例。 using語句之后檢查SynchronizationContext.Current 然后,您將面臨一個經典的僵局,該僵局由Stephen Cleary在他的“不要阻止異步代碼”中很好地解釋。

是的,您不會阻塞自己,但是MSTest / NUnit會阻塞,因為它足夠聰明,可以識別NeverCompletes方法的async Task簽名,然后Task.Wait返回的Task執行類似Task.Wait的操作。 因為原始的單元測試線程沒有消息循環,也沒有泵送消息(這與WindowsFormsSynchronizationContext所期望的不同),所以NeverCompletes內部的await延續永遠不會執行, Task.Wait只是掛起了等待。

就是說, MessageLoopWorker僅設計為傳遞給MessageLoopWorker.Run async方法范圍內創建並運行WinForms對象,然后完成該操作。 例如,以下內容不會被阻止:

[TestMethod]
public async Task NeverCompletes()
{
    await MessageLoopWorker.Run(async (args) =>
    {
        using (new Form()) ;
        return Type.Missing;
    });
}

並非旨在與多個MessageLoopWorker.Run調用中的WinForms對象一起使用。 如果這是您需要的,則可能需要從這里查看我的MessageLoopApartment ,例如:

[TestMethod]
public async Task NeverCompletes()
{
    using (var apartment = new MessageLoopApartment())
    {
        // create a form inside MessageLoopApartment
        var form = apartment.Invoke(() => new Form {
            Width = 400, Height = 300, Left = 10, Top = 10, Visible = true });

        try
        {
            // await outside MessageLoopApartment's thread
            await Task.Delay(2000);

            await apartment.Run(async () =>
            {
                // this runs on MessageLoopApartment's STA thread 
                // which stays the same for the life time of 
                // this MessageLoopApartment instance

                form.Show();
                await Task.Delay(1000);
                form.BackColor = System.Drawing.Color.Green;
                await Task.Delay(2000);
                form.BackColor = System.Drawing.Color.Red;
                await Task.Delay(3000);

            }, CancellationToken.None);
        }
        finally
        {
            // dispose of WebBrowser inside MessageLoopApartment
            apartment.Invoke(() => form.Dispose());
        }
    }
}

或者,如果您不擔心測試的潛在耦合,甚至可以跨多種單元測試方法使用它,例如(MSTest):

[TestClass]
public class MyTestClass
{
    static MessageLoopApartment s_apartment;

    [ClassInitialize]
    public static void TestClassSetup()
    {
        s_apartment = new MessageLoopApartment();
    }

    [ClassCleanup]
    public void TestClassCleanup()
    {
        s_apartment.Dispose();
    }

    // ...
}

最后, MessageLoopWorkerMessageLoopApartment都沒有設計為與在不同線程上創建的WinForms對象一起使用(無論如何這幾乎不是一個好主意)。 您可以根據需要擁有任意多個MessageLoopWorker / MessageLoopApartment實例,但是一旦在特定MessageLoopWorker / MessageLoopApartment實例的線程上創建了WinForm對象,則應進一步對其進行訪問並僅在同一線程上對其進行適當銷毀。

暫無
暫無

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

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