将对象所有权传递给WPF应用程序中的其他线程

时间:2013-08-21 10:24:54

标签: c# wpf multithreading c#-4.0

编辑1:

显然,我已经开始以不正确的方式在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对象的所有权,还是一个好的解决方法?

2 个答案:

答案 0 :(得分:3)

首先,我想说WPF 是开发除最简单游戏之外的所有游戏的好框架......我建议使用微软的XNA之类的游戏框架。

但是,如果您坚持使用WPF,那么我想提请您注意CompositionTarget.Rendering事件。它主要使用主机的帧速率来渲染图形通道,从而避免使用定时器。

您还应该查看MSDN上的How to: Render on a Per Frame Interval Using CompositionTarget页面,以获取更多有用的信息和代码示例。

此外,请阅读“WPF Control Development Unleashed:构建高级用户体验”一书中的摘录:

  

有些读者可能会认识到这种方法与高端图形之间的相似性   像DirectX这样的子系统。不要错误使用CompositionTarget.Rendering进行良好的注入   指向创建基于WPF的游戏图形引擎。高端显卡和超高清   帧速率不是WPF动画这一特定方面的目标。

     

与DispatcherTimer方法类似,基于CompositionTarget.Rendering的动画   也没有时间限制。但是,这些事件与生成的渲染线程同步   比DispatcherTimer更流畅的动画。也没有必要开始和   停止计时器,虽然您可能需要分离并附加事件处理程序以进行改进   性能

更新&gt;&gt;&gt;

我发现这只是一个课程项目,到目前为止我会忽略我之前的评论和你的代码示例。当已经存在时,不要尝试创建新的渲染系统。相反,你应该遵循这种方法:

创建实现INotifyPropertyChanged界面的数据对象,并拥有XYDirectionVector(可能是Size结构)公共属性。< / p>

为您的Move类添加Swim方法(或Fish方法),在其中更新数据对象的XY属性,具体取决于DirectionVector属性的值。

向您的用户界面添加ListBox控件。

创建一个集合属性来保存数据对象,添加项目并将集合绑定到ListBox.ItemsSource属性。

创建DataTemplate以定义Fish个对象的外观......您可以使用Path类来绘制它们,甚至使用RotateTransform来旋转它们(角度可以从DirectionVector属性计算)。在DataTemplate中,您可以将XY属性绑定到“边距”属性。

最后,添加一个无限循环(可能带有一个break out选项)并在该循环中迭代数据对象的集合并在每个循环上调用Move()。这将更新数据对象在ListBox

中的位置

答案 1 :(得分:3)

作为一般规则,唯一可以改变WPF中线程效忠的对象是那些派生自Freezable的对象。 (例如,Model3D是可冻结的,因此,LightGeometryModel3D之类的内容。)

直接参与可视树的元素不是来自Freezable。它们来自Visual(通常,但并非总是如此,通过FrameworkElement)。因此,可视元素永远与您创建它们的线程相关联。 Freezables通常是描述性项目,告诉可视树元素该做什么。例如,画笔(无论是实体,渐变填充,图像画笔等等)都是freezables,但是要用画笔做某事,你需要将它用作某些视觉元素的属性(即不可冻结的东西),例如{ {1}}的{​​1}}。

所以Fill属于这个类别 - 它是3D模型的描述,但它实际上并不知道如何渲染自己。您将此描述提供给一些知道如何呈现模型的视觉元素(例如Rectangle)。

因此可以在工作线程上构建Model3D,然后将其传递给UI线程。

但是,您只能通过调用Viewport3D来冻结它后,从某个线程开始使用freezable对象,而不是创建它的那个线程。顾名思义,这可以防止进一步修改。冻结freezable后,它不再与任何特定线程相关联,因此您可以从任何您喜欢的线程中使用它。

此处的预期用法模型是:

  1. 在工作线程上构建复杂的东西
  2. 冻结
  3. 将其附加到知道如何在UI线程中呈现它的内容
  4. 如果你想构建一个复杂的Model3D,这可能是合适的,这个复杂的Freeze需要花费很长时间来构建,并且你不想让应用程序在发生这种情况时没有响应。

    但是,如果您需要随着时间的推移可以修改模型,这没有任何用处。如果这就是你需要的东西(听起来就是这样)那么你往往别无选择,只能在UI线程上创建模型 - 如果你创建一个你从未实际冻结的freezable(因为你需要能够改变它)那么你必须在将渲染它的同一个线程上创建它。当您想要更新模型时,您需要确保在UI线程上完成更新,或者您可以使用数据绑定,它能够处理任何线程上的更改通知事件,并且它将编组那些到UI线程的你。

    但是,我想知道你是否真的需要多线程。你给出了原因

      

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

    这不是使用单独线程的真正原因。没有什么能阻止UI线程执行模型更新并响应UI输入。您只需要确保定期更新模型的代码将控制权返回给事件循环。

    使用单独线程的唯一原因是,确定模型更新应该是什么的计算在计算上是昂贵的。例如,如果您编写的代码执行某些进程的复杂且高度详细的模拟,然后呈现结果,则在工作线程上执行计算以使UI保持响应可能是有意义的。但即便如此,一旦完成这些计算,您需要确保根据这些计算结果对模型所做的更新是在UI线程上完成的,而不是工作线程。

    值得考虑的是,您是否可以逐步建立新模型。如果您丢弃旧模型并立即将其替换为新构建的模型,用户可能实际上并未注意到。这可以使您在工作线程上构建整个模型,因为您可以将其冻结。如果你每次都建立一个新的模型,那么冻结就是安全的,因为只要你想改变一些东西,你只需要建立一个新模型而不是更新旧模型。

    另一种变化是拥有一个主要由冷冻碎片组成的模型,由一些未冻结的顶级元素包含。