2013-08-21 33 views
3

編輯1:傳遞對象所有權給其他線程在WPF應用程序

顯然,我已經開始以不正確的方式來實現在WPF環境中的3D渲染。 Ofc下面提供了我的問題的解決方案,但我建議閱讀Sheridan答案的更新並使用他的建議來實現此目的。它不僅安全,而且性能也更好。儘管理解它有點複雜,但一旦理解了它,就可以開始在WPF中呈現多個3D應用程序。 感謝您的協助Sheridan!


問題;我在WPF中很新,我想用WPF設計一個連續的渲染(如在遊戲應用程序中)。我正在使用多線程來提供更好的UI控制(開始/停止按鈕fe)。或者由於使用無限循環渲染3D世界,可能會導致事件發生。

但是,我的問題是,當運行該程序時,我得到一個Invalid operation was unhandled錯誤。問題是有一個對象是主線程的屬性,因此新線程可能無法訪問它。

從XAML文件,

<Grid> 
    <!-- ui controls omitted ... --> 
    <Viewport3D Name="myViewport" ClipToBounds="True"> 
     <!-- all inits, camera, pos, ... --> 
    </Viewport3D> 
</Grid> 
在主類

;

/// <summary>this method is done to render the 3D app in other thread.</summary> 
private void Runtime(Viewport3D vp) { 
    System.Diagnostics.Debug.WriteLine("runtime "); 
    Render3D r3d = new Render3D(vp); 
    // actual startup 
    while (keepRunning) { 
     r3d.Init3D(); 
    } 
} 

/// <summary>this method toggles the game runtime</summary> 
/// <param name="sender"></param> 
/// <param name="e"></param> 
private void StartOrStop(object sender, RoutedEventArgs e) { 
    keepRunning = !keepRunning; 
    if (keepRunning) { 
     buttonStartStop.Content = "Stop"; 
     // thread 
     t1 = new Thread(() => Runtime(myViewport)); 
     t1.Start(); 
    } 
    else { 
     buttonStartStop.Content = "Start"; 
     t1.Abort(); 
    } 
} 

3DViewport對象在XAML文件中初始化。這就是爲什麼我將它傳遞給新線程,它可以創建一個使用該3DViewport類的對象。

下面是Render3D類的示例。

// constructor 
internal Render3D(Viewport3D v) { 
    currViewport = v; 
} 

/// <summary>get called in loops to render gfx</summary> 
internal void Init3D() { 
    // clear rendered view 
    ClearRenderWindow(); 
    // add landscape 
    AddLandScape(); 
} 

/// <summary>clear window to re-render gfx</summary> 
private void ClearRenderWindow() { 
    ModelVisual3D mv; 

    // ***** error got caught here below ****** 
    for (int i = currViewport.Children.Count - 1; i >= 0; i--) { 
     mv = (ModelVisual3D)currViewport.Children[i]; 
     if (mv.Content is DirectionalLight == false) currViewport.Children.Remove(mv); 
    } 
} 

錯誤發生在currViewport.Children.Count方法。如前所述,問題在於當前線程沒有該對象的所有權。這是我多線程體驗中第一次面對這個問題。我已經四處搜尋,但找不到解決方案。

有誰知道如何通過Viewport3D對象的所有權,或一個很好的解決方法?

回答

3

首先,我謹說,WPF是開發所有,但最簡單的遊戲一個良好的框架......我會建議使用像微軟的XNA,而不是一個遊戲框架。

但是,如果您堅持使用WPF,那麼我想將CompositionTarget.Rendering事件引起您的注意。它基本上使用主機的幀速率來渲染圖形,避免使用定時器。

您還應該查看MSDN上的How to: Render on a Per Frame Interval Using CompositionTarget頁面以獲取更多實用信息和代碼示例。

另外,請閱讀本書的WPF控件開發偷跑:構建高級用戶體驗「:這種提取物

有些讀者可能會認識到這一點的方法和更高端的顯卡 子系統喜歡的DirectX之間的相似性。不要把CompositionTarget.Rendering錯誤地指向創建一個基於WPF的遊戲圖形引擎。高端圖形和超高幀率並不是WPF動畫這一特定方面的目標。

與DispatcherTimer方法類似,基於CompositionTarget.Rendering 的動畫也沒有時間限制。然而,這些事件會與渲染線程同步,導致 比DispatcherTimer更平滑的動畫。也不需要啓動並停止計時器,但您可能必須分離並附加事件處理程序以提高 的性能。

UPDATE >>>

已經發現,這僅僅是一個課程項目,我會忽略我以前的評論和你的代碼示例至今。當已經有一個渲染系統時,不要試圖創建一個新的渲染系統。相反,你應該遵循這個方法:

創建實現了INotifyPropertyChanged接口,並有XYDirectionVector(可能是一個Size結構)公共屬性的數據對象。

添加Move方法(您Fish類或Swim法),其中更新的數據對象XY性質取決於DirectionVector屬性的值。

ListBox控件添加到您的用戶界面。

創建一個集合屬性來保存您的數據對象,添加項目並將集合綁定到ListBox.ItemsSource屬性。

創建DataTemplate來定義Fish對象是什麼樣子......你可以使用Path類來吸引他們,甚至用RotateTransform將其旋轉(角度可以從DirectionVector屬性來計算)。在DataTemplate中,可以將XY屬性綁定到「邊距」屬性。

最後,添加一個無限循環(可能帶有分支選項),並在該循環中遍歷數據對象的集合,並在每個對象上調用Move()。這將更新數據對象在ListBox中的位置。

+0

謝謝你的回覆。這是我的課程項目。我知道有更好的選擇(我甚至問我是否被允許使用XNA或DirectX,因爲WPF太有限了)。這是一個「潛水」遊戲,只是在游泳和會見魚類。 :-)嘿,看看這個事件的方法。但是,它甚至需要使用單線程應用程序還是我錯了? – KarelG

+0

再次感謝您。我已經完成了,現在我可以渲染我想要的3D內容。但是,我不知道我應該如何處理'INotifyPropertyChanged'事件。每次Move()調用後我都會觸發它,但是,那又怎樣? – KarelG

+0

很好。在CodeProject上看看[這篇文章](http://www.codeproject.com/Articles/41817/Implementing-INotifyPropertyChanged)...特別是前三個代碼部分。這個想法是每次屬性更新時(來自Move方法),每個屬性都會通知'INotifyPropertyChanged'接口。接口將自動更新UI以反映更改(如果您已正確綁定UI中的屬性)。 – Sheridan

3

作爲一般規則,唯一可以更改WPF中的線程效應的對象是從Freezable派生的那些對象。 (例如,Model3D是凍結的,所以,所以,事情就像LightGeometryModel3D)直接參與的可視化樹不從Freezable獲得

元素。它們來自Visual(通常,雖然不總是,通過FrameworkElement)。因此,視覺元素永遠與您創建它們的線程相關聯。 Freezables通常是描述性的項目,它告訴可視樹元素要做什麼。例如,畫筆(無論是實體,漸變填充,圖像畫筆還是其他)都是可凍結的,但要使用畫筆做某些事情,您需要將它用作某些可視元素的屬性(即不是可凍結的元素),例如,Fill的一個Rectangle

所以Model3D屬於這個類別 - 它是一個3D模型的描述,但它實際上並不知道如何呈現自己。您可以將此說明提供給某些知道如何渲染模型的可視元素(例如Viewport3D)。

因此,可以在工作線程上構建Model3D,然後將其傳遞給UI線程。

但是,只有在通過調用Freeze將其凍結之後,才能從其創建的線程以外的某個線程開始使用可凍結對象。顧名思義,這阻止了進一步的修改。一旦凍結的凍結,它不再與任何特定的線程關聯,所以你可以從任何你喜歡的線程使用它。

預期的使用模型這裏是:

  1. 建立一些複雜的一個工作線程
  2. 它凍
  3. 它附加的東西,知道如​​何呈現在UI線程

如果您想構建複雜的需要花費很長時間構建的複雜的Model3D,並且您不想讓應用程序不響應這是在發生。

但是,如果您需要該模型隨着時間的推移可修改,則這沒有任何用處。如果這就是你所需要的(聽起來像是這樣),那麼你往往別無選擇,只能在UI線程上創建模型 - 如果你創建了一個永遠不會實際凍結的凍結文件(因爲你需要修改它)那麼你必須在將渲染它的同一個線程上創建它。當你想更新模型時,你需要確保更新在UI線程上完成,或者你可以使用數據綁定,它能夠處理任何線程上的變化通知事件,並將它們編組到UI線程您。

但是,我想知道你是否真的需要多線程。你給理由爲

提供更好的UI控制(開始/停止按鈕fe)。

這不是真正的使用單獨線程的原因。沒有什麼能夠阻止UI線程執行對模型的更新並且也響應UI輸入。您只需確保定期更新模型的代碼將控制返回到事件循環。

使用單獨線程的唯一原因是,如果計算確定模型的更新應該是計算成本高昂的。例如,如果您編寫的代碼對某個進程執行復雜且高度詳細的模擬,然後呈現結果,則可以在工作線程上執行計算以使UI保持響應狀態,這很有意義。但即便如此,一旦這些計算完成,您需要確保根據這些計算結果對模型所做的更新在UI線程上完成,而不是在工作線程上完成。

這可能是值得考慮的,你是否可以擺脫建立一個新的模型每一次。用戶可能實際上沒有注意到如果放棄舊模型並立即用新建模型替換它。這可以使您能夠在工作線程上構建整個模型,因爲您可以將其凍結。如果每次都建立一個新模型,可以很容易地凍結,因爲任何時候你想改變某些東西,你只需要建立一個新模型而不是更新舊模型。

還有另一種變化是有一個模型,其中大部分由一些未凍結的頂層元素包含的凍結塊組成。

+0

有趣。我決定做多線程,因爲UI之前的點擊事件在被處理之前由於使用無限循環而處理掉了。如果你知道更容易的解決方法來捕獲UI事件(我不知道要使用哪些方法或語句),那麼我的問題就解決了。可能我在這裏讓自己變得困難了...... – KarelG

+0

不要使用無限循環。可能你的循環的目的是不斷更新3D模型以支持移動? Sheridan提到 - 而不是使用循環,只是處理CompositionTarget.Rendering事件 - 這會每隔一幀調用一次,直到您移除處理程序。在這個事件處理程序中,執行您在無限循環的一次迭代中完成的所有工作。 –

相關問題