1

Bit of a special case here, because I'm using a library called helix-toolkit but bear with me.

The thing is I would like to parallelize the creation of model objects in my code using a backgroundworker.

I know that there is a big issue with mutlithreading and working on UI elements but maybe somehow there is a workaround.

Here is the rough structure of my code:

First file in which I create the Backgroundwoker, splitting the workload for creating Geometry3D objects and finally calling SetModelGeometry to bind the geometries to the viewport. The second file shows how the binding is done.

MainWindow.xaml.cs

  private void Draw_Building()
        {
            _worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
            _worker.DoWork += Draw_Building_DoWork;
            _worker.ProgressChanged += DrawBuilding_ProgressChanged;
            _worker.RunWorkerCompleted += DrawBuilding_RunWorkerCompleted;
            _worker.RunWorkerAsync(10000);

        }

        private void Draw_Building_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            // returns a list containing Geometry3D objects ( created by meshBuilder.ToMeshGeometry3D() )
            Geometryhandler.Draw_Building(sender, e); 
        }

        private void DrawBuilding_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            StatusProgressBar.Value = e.ProgressPercentage;
            var i = (int)e.UserState;
            var actualComponent = MyBuildingComponents.First(c => c.Id == i);
            LblStatusbarInfo.Text = "Currently meshing element #" + actualComponent.Globalid + " (" +
                                    actualComponent.Objectname + ")";
            StatusProgressbarMsg.Text = "Meshing (" + e.ProgressPercentage + " %)";
        }

        private void DrawBuilding_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            StatusProgressbarMsg.Text = "-";
            StatusProgressBar.Value = 0;
            LblStatusbarInfo.Text = "Meshing completed.";

            Geometry = e.Result as List<MeshIdandGeometry>;

            // creates MeshGeometryModel3D objects and binds them to the viewport using the List of Geometries
            MainViewModel.SetModelGeometry(Geometry);

        }

MainViewModel.cs

    public void SetModelGeometry(List<MeshIdandGeometry> geometry)
    {

        MyModelGeometry = new Element3DCollection();

        if (geometry != null)
        {
            foreach (var mygeometry in geometry)
            {

                var s = new MeshGeometryModel3D
                {
                    Geometry = mygeometry.Geometry,
                    Material = mygeometry.Material,
                };
                this.MyModelGeometry.Add(s);

                s.Attach(MyModelViewport.RenderHost);
            }

        }

        this.OnPropertyChanged("MyModelGeometry");
    }

My problem at the moment is the following error message:

The calling thread cannot access this object because a different thread owns it.

which is thrown in the SetModelGeometry function when trying to attach the ModelGeometry to the viewport.

I guess the compiler is complaining about the fact that the geometries were created in different threads, to which he has no access now.

Is there any workaround/solution without destroying the parallel execution of the DrawBuilding function?

EDIT:

EDIT 2: posted the wrong version of the Draw_Building method

The Draw_Building method in the Geometryhandler:

    public void Draw_Building(object sender, DoWorkEventArgs e)
    {

        var geometry = new List<MeshIdandGeometry>();

        var standardMaterial = new PhongMaterial()
        {
            AmbientColor = SharpDX.Color.LightGray,
            //DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
            //DiffuseMap = new BitmapImage(new System.Uri(@"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
            //NormalMap = new BitmapImage(new System.Uri(@"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
        };

        var max = _mainWindow.MyBuildingComponents.Count;
        var i = 1;

        // Loop over building components
        foreach (var component in _mainWindow.MyBuildingComponents)
        {

            //if (i == 5) break;
            var component1 = component;
            var componentTriangles = _mainWindow.MyTriangles.Where(triangle => triangle.ComponentId == component1.Id);

                var meshBuilder = new MeshBuilder(true, true, true);

            // Loop over triangles in building element
            foreach (var triangle in componentTriangles)
            {
                var triangle1 = triangle;

                var p1 = _mainWindow.MyVertices.Find(
                    vt => vt.Id == triangle1.PointId1);
                var p2 = _mainWindow.MyVertices.Find(
                    vt => vt.Id == triangle1.PointId2);
                var p3 = _mainWindow.MyVertices.Find(
                    vt => vt.Id == triangle1.PointId3);
                if (p1 != null && p2 != null && p3 != null)
                {
                    //meshBuilder.AddTriangle(new Vector3((float)p1.X, (float)p1.Y, (float)p1.Z),
                    //    new Vector3((float)p2.X, (float)p2.Y, (float)p2.Z),
                    //    new Vector3((float)p3.X, (float)p3.Y, (float)p3.Z));

                    // coordination are switched to match the coordinate system in SharpDX viewport
                    meshBuilder.AddTriangle(new Vector3(-(float)p1.Y, (float)p1.Z, -(float)p1.X),
                        new Vector3(-(float)p2.Y, (float)p2.Z, -(float)p2.X),
                        new Vector3(-(float)p3.Y, (float)p3.Z, -(float)p3.X));
                }
            }
            var mesh = meshBuilder.ToMeshGeometry3D();

            var meshandtriangle = new MeshIdandGeometry
            {
                Id = component1.Id,
                Geometry = mesh,
                Material = standardMaterial,
            };
            geometry.Add(meshandtriangle);

            i++;
            var progressPercentage = Convert.ToInt32(((double)i / max) * 100);
            var backgroundWorker = sender as BackgroundWorker;
            backgroundWorker?.ReportProgress(progressPercentage, component1.Id);
        }

        e.Result = geometry;

    }
Daniel
  • 107
  • 1
  • 13
  • Try Freeze and Clone – egse Oct 24 '16 at 14:11
  • Hi egse, sorry I only saw your comment now. How would I use that exactly? I saw that freeze is implemented for a Media3D.GeometryModel3D objects for example, but didn't find an equivalent for the model objects the sharpDX branch are using. – Daniel Oct 31 '16 at 12:28
  • Yes, you are right. Then I think it is not the Geometry which causes the error but the Material. If you show me the content of the Geometryhandler.Draw_Building method i may can help more. – egse Nov 02 '16 at 08:27
  • Thanks yet again egse :) , I put the code in my Question above as an edit. – Daniel Nov 02 '16 at 12:47
  • Try to create the MeshGeometryModel3D objects in the UI thread. Just create the mesh in the Backgroundworker. And since you also not create a Material (only use of _standardMaterial) you also should assign it in the UI thread. General: Do not create instances of classes which inherit from DependencyObject in another thread than the UI – egse Nov 02 '16 at 12:57
  • Are you _sure_ that the model creation is your bottleneck? Are you actually experiencing performance issues? High performance geometry is rarely created in parallel. Can you do more work in a single call - API call count is a big deal. You want to hit the API as few times as possible. What happens if you remove the progress and logging code? What happens if you run it in release mode? – Gusdor Nov 02 '16 at 12:59
  • @egse: I will give this a try and tell you how this worked out, thanks egse for your time. – Daniel Nov 02 '16 at 13:08
  • @Gusdor It definitely takes some seconds, so one issue I'm trying to solve here is the responsiveness of the UI. I thought this is a use-case for Backgroundworker. I'm open for suggestions, still pretty new to all of this – Daniel Nov 02 '16 at 13:13
  • @Daniel How many meshes do you have? Try pushing the 'meshing' function for each mesh to the dispatcher in its own frame using `SynchronizationContext.Current.Post()` This way the UI will be able to update between each mesh. – Gusdor Nov 02 '16 at 13:19
  • @egse Ahhh now it works. The DrawBuilding() method I posted above was actual the wrong version( now updated to the original, problemetic method ). In the end the problem was, that while I did create the MeshGeometryModel3D object in the UI thread, the Material was created inside the scope of the Backgroundworker. Didn't think about the material as a Dependecy Object as well. Thank you egse :D – Daniel Nov 03 '16 at 09:37
  • 1
    @Gusdor thanks for the suggestion. Honestly I've never heard of SynchronizationContext. I did a bit of research and I think I got the idea, but still went with the simpler solution^^. Thank you nonetheless! P.S Thsi is where I did my research : http://stackoverflow.com/questions/18097471/what-does-synchronizationcontext-do – Daniel Nov 03 '16 at 10:25
  • @Daniel can you answer this question with an overview of your solution? Might be useful to others :) – Gusdor Nov 03 '16 at 10:26

1 Answers1

1

Big thanks to @egse for finding a solution.

Part of the code that causes the problem:

    var standardMaterial = new PhongMaterial()
    {
        AmbientColor = SharpDX.Color.LightGray,
        //DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
        //DiffuseMap = new BitmapImage(new System.Uri(@"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
        //NormalMap = new BitmapImage(new System.Uri(@"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
    };

Basically the problem with the above code is that the material is created as a local variable inside the scope of the backgroundworker. This causes problems with ownership when the UI thread tries to enter the material objects.

The solution to this problem is to make sure that the material is created by the UI thread (e.g in this case, the constructor of the Geometryhandler)

TL;DR: Do not create instances of classes which inherit from DependencyObject in another thread than the UI.

Daniel
  • 107
  • 1
  • 13