2009-10-13 56 views
0

我想平行構建在Windows窗體之上的3D體素編輯器,它使用raycaster渲染以便劃分屏幕並獲取池中的每個線程以渲染其中的一部分應該是微不足道的。Windows窗體上的多線程

問題出現在Windows窗體的線程必須以STA運行 - 我可以讓其他線程啓動並完成工作,但在等待它們完成時阻塞主線程會導致奇怪的隨機死鎖。

保持主線程暢通也是一個問題 - 例如,如果用戶使用填充工具,輸入將在渲染過程中處理,這將導致「中間」圖像(對象部分着色,例如)。在每個幀之前複製整個圖像是不可行的,因爲如果必須在每一幀中複製,卷的大小足以抵消任何性能增益。

我想知道是否有任何解決方法讓氨線程以阻止用戶的方式阻止,但不會實際阻塞,但會延遲輸入的處理直到下一幀。

如果這是不可能的,是否有更好的設計來處理這個問題?

編輯:閱讀anwsers我認爲我並不清楚raycaster實時運行,因此顯示進度對話框將不起作用。不幸的是,FPS足夠低(取決於各種因素5-40),以便幀之間的輸入產生不希望的結果。

我已經試圖實現它阻塞UI線程和使用ThreadPool的一些線程來處理,並且它工作正常,除了STA的這個問題。

+0

你有什麼僵局嗎? – SLaks 2009-10-13 01:29:22

+0

隨機輸入事件。單擊任務欄上最小化的應用程序通常會執行此操作 - 有時會發出Windows蜂鳴聲(即錯誤嗶聲),有時會發生崩潰。 – 2009-10-13 03:17:01

回答

1

我是否錯過了某些東西,或者您是否可以在渲染幀時將渲染控件(和生成輸入事件的其他控件)設置爲禁用?這將防止不必要的輸入。

如果您仍想在渲染過程中接受事件,但不想在下一幀之前應用它們,則應該啓用控件並將事件的詳細信息發佈到輸入隊列中。然後應該在每一幀的開始處理該隊列。

這會影響到用戶仍然可以單擊按鈕並與UI交互(GUI線程不會阻止),並且直到下一幀開始時,渲染器纔會看到這些事件。在5 FPS時,用戶應該看到他們的事件在400毫秒最差情況下(2幀)處理,但速度不夠快,但比線程死鎖好。

也許是這樣的:

Public InputQueue<InputEvent> = new Queue<InputEvent>(); 

// An input event handler. 
private void btnDoSomething_Click(object sender, EventArgs e) 
{ 
    lock(InputQueue) 
    { 
     InputQueue.Enqueue(new DoSomethingInputEvent()); 
    } 
} 

// Your render method (executing in a background thread). 
private void RenderNextFrame() 
{ 
    Queue<InputEvent> inputEvents = new Queue<InputEvent>(); 

    lock(InputQueue) 
    { 
     inputEvents.Enqueue(InputQueue.Dequeue()); 
    } 

    // Process your input events from the local inputEvents queue. 
    .... 

    // Now do your render based on those events. 
    .... 
} 

哦,而且做在後臺線程你的渲染。你的UI線程很珍貴,它應該只做最微不足道的工作。 Matt Brundell對BackgroundWorker的建議有很多優點。如果它不做你想要的,ThreadPool也是有用的(並且更簡單)。更強大(和複雜)的替代品是CCRTask Parallel Library

+0

這似乎是一個好主意,我會嘗試一下,讓它知道它是否有效。 – 2009-10-13 14:03:23

+0

它的工作,非常感謝! – 2009-10-13 21:58:03

0

使用ShowDialog顯示模式「請稍候」對話框,然後在您的渲染完成後關閉它。

這將阻止用戶與表單交互,同時仍然允許您調用UI線程(這可能是您的問題)。

+0

其實它是ShowDialog,不是ShowModal;) – 2009-10-13 01:33:25

+0

固定它;謝謝。 – SLaks 2009-10-13 01:35:35

+0

不顯示對話框停止父窗體的所有處理,直到子窗體關閉? – 2009-10-13 01:36:08

6

這是一個常見問題。使用Windows窗體你可以只有一個UI線程。不要在UI線程上運行你的算法,因爲那時UI將會凍結。

我建議運行您的算法並在更新UI之前等待它完成。一個名爲BackgroundWorker的課程是爲了完成這件事而預先構建的。

編輯:

對UI線程的另一個事實是,它處理所有的鼠標和鍵盤事件,與發送到窗口系統消息一起。 (Winforms實際上只是由一個漂亮的API包圍的Win32)。如果UI線程飽和,則不能擁有穩定的應用程序。

在另一方面,如果你開始其他幾個線程,並嘗試與他們直接在屏幕上繪製,你可能有兩個問題:

  1. 你不應該吸取與任何UI線程但UI線程。 Windows控件不是線程安全的。
  2. 如果您有很多線程,它們之間的上下文切換可能會導致性能下降。

請注意,您(和我)不應主張性能問題,直到它被測量。您可以嘗試在內存中繪製一幀,並在適當的時間將其交換。它叫做double-buffering,在Win32繪圖代碼中非常常見,可以避免屏幕閃爍。

老實說,我不知道這是可行的與您的目標幀率,或者如果你應該考慮像OpenGL更加以圖形爲中心的庫。

+0

我認爲我不是很清楚,意圖是UI在繪製框架之前會呈現凍結狀態(5-40 FPS,取決於各種參數)。如果它保持更新,那麼圖像可能會在幀之間部分改變,並且看起來很奇怪。 – 2009-10-13 03:07:33

-1

在每個幀之前複製整個圖像是不可行的,因爲如果必須在每一幀中複製,卷的大小足以抵消任何性能增益。

然後不要複製每一幀的離屏緩衝區。

0

如果您不希望BackgroundWorker提供的所有功能都可以簡單地使用ThreadPool.QueueUserWorkItem將某些內容添加到線程池並使用後臺線程。在後臺線程執行操作時可以很容易地顯示某種進度,因爲您可以提供委託回調來在特定後臺線程完成後通知您。看看ThreadPool.QueueUserWorkItem Method (WaitCallback, Object)看看我指的是你。如果你需要更復雜的東西,你總是可以使用APM異步方法來執行你的操作。

無論哪種方式,我希望這有助於。

編輯:

  1. 通知用戶以某種方式的改變正在對UI做。
  2. 在使用ThreadPool的(許多)後臺線程上執行您需要對UI執行的操作。
  3. 對於每個操作,請保留對操作狀態的引用,以便知道它何時在WaitCallback中完成。也許把它們放在某種類型的散列/集合中以保留它們。
  4. 每當一個操作完成時,將其從包含ref的集合中移除到已執行的操作。
  5. 所有操作完成後(散列/集合)沒有更多引用在其中呈現應用了更改的UI。或者可能會逐步更新用戶界面

我在想,如果您在執行操作時對UI進行了如此多的更新,那是什麼導致了您的問題。這也是爲什麼我建議使用SuspendLayout,PerformLayout,因爲你可能已經執行了如此之多的UI更新,主線程已經不知所措。

雖然我不是線程專家,只是想通過自己來思考。希望這可以幫助。

+0

我已經嘗試過使用ThreadPool,但是我無法保持循環運行並顯示進度條,因爲它是實時渲染器(速度不夠快,以避免由圖像之間的圖像部分更改導致的工件)。如果我在等待渲染完成時阻塞UI線程,則會隨機發生死鎖。 – 2009-10-13 03:13:20

+0

您可以使用APM報告每個項目的進度。儘管如此,我認爲你可能會遇到一個有意義的確定性進展報告。我不認爲你想在UI線程上執行任何阻塞來實現這一點。 當您更新UI以反映更改時,您是否正在對各個控件執行SuspendLayout和ResumeLayout?對我來說,似乎你會想要完全掛起SuspendLayout,直到完成更改爲止,但我並不確定我完全理解了你的問題。 – 2009-10-13 03:29:51