Rhino API - Multithreaded Object Manipulation (are calls lazy?)

Hello all,

Question: When I call, for example, ObjectTable.GetSelectedObjects Method to get the IEnumerable that is currently selected, how much of this is actually lazily done?

For example, I call the ObjectTable.GetSelectedObjects to get, say, the 3 PointObject that are currently selected. Usually I flatten out the IEnumerable into either a List or an Array, because I want to make sure that the communication with Rhino is finished.
Basically, I want to get the list and let Rhino’s thread rest for a while (I don’t want it to work each time the IEnumerable is yielded).
I need to be sure that I can read and write to these objects in a thread that is not the Rhino thread (thread that called the RunCommand of my command).

But what about the inner data of each PointObject (or of any other RhinoObject)? For example:
1- Is the Geometry.Location written into the local C# RhinoObject when it is created (returned from Rhino), or when it is first accessed?
2- Is the Attributes property written into the local C# RhinoObject when it is created (returned from Rhino), or when it is first accessed?
3- What about the UserStrings of the Object’s attribute? Are these calls (Get,Set,Delete) lazy? Do they operate in the local C# copy object or do they do a round-trip to the Rhino doc to fetch this information?

I suppose that 1 and 2 are written at object’s creation (because they don’t call methods), while the UserStrings of the object’s attributes are lazy.

Thank you for all clarification you can give me.

Commands are called on the main thread.

RhinoDoc access always should be on the main thread, it isn’t safe to access from other threads.

  1. Not all Geometry has a Location, pretty much only Points have that.
  2. When getting an object from the document all the data is there.
  3. Changing objects you can do all you like, but changes won’t appear in the document until you commit these. And that again is something you should do in the main thread.

Generally once you have an object changes will be local to that object. As said, these need to be committed to the document before they are “real”.

Hello Nathan,

Thank you for your reply. I am running some tests and I found a weird behaviour. All is running on the main thread in this test, and I am having a VERY weird behaviour.

Basically, I have various lists of points that are in different layers.

The weird behaviour is that the code AS IS does not update Rhino. Not the color, nor the UserString of the attribute. This is, I guess, expected, as I am not calling the obj.CommitChanges() function.

BUT… If I comment the line that sets the color, the UserString gets updated - even if I don’t call the obj.CommitChanges() function.

RhinoDoc Doc; // Is the doc reference that comes as parameter to the RunCommand function.

// Gets the cloud list
Layer cloudBase = Doc.AddOrGetLayer("PointCloud");
List<Layer> cloudLayers = Doc.Layers.Where(l => l.ParentLayerId == cloudBase.Id).ToList();

// Gets the points
Dictionary<Layer, List<RhinoObject>> objDic = new Dictionary<Layer, List<RhinoObject>>();
foreach (var item in cloudLayers)
{
    objDic.Add(item, SLData.Doc.Objects.FindByLayer(item).ToList());
}

// Now we simply manipulate the data of the RhinoObjects
foreach (var item in objDic)
{
    foreach (var obj in item.Value)
    {
        obj.Attributes.ObjectColor = Color.Blue;
        obj.Attributes.SetUserString("test", "test");
    }
}

That looks like a bug to me. I have logged RH-71536 Attributes properties behave inconsistently.

1 Like

Hi Nathan,

Thank you so much for your assistance. I can’t say I’m happy that a bug was found, but that is at least something.

As far as my original issue goes, the behaviour seems to be undefined. Sometimes the properties of the RhinoObject are already copied to the local copy of the object and sometimes the information is fetched from the RhinoDoc under the hood (using COM, I suppose, or at least Marshalling).

For my use case it becomes very relevant for me to know what are the properties that are copied (and thus can be safely write/read from other threads down the line) and the properties that must fetch/transfer the information from/to the rhino document (and thus must be executed on the main thread).

Do you have any idea how I could monitor from the client side if a certain read/write onto the properties of a given RhinoObject instance fetch/sends the data from/to Rhino or if it operates locally (only commiting the changes after a call to CommitChanges).

Again, thank you very much,

The RhinoCommon SDK is just a wrapper around the Rhino C++ SDK.

Well, I noticed that the C# wrappers of the classes actually exposes what is happening under the hood, so I can easily check what are the calls that are being marshalled.

Thank you once again. I feel I have what I need to push forward.

This is just a logging for someone else that might reach this in a future googling:
The data is lazily filled. Native calls are made for virtually all properties.

        //
        // Summary:
        //     Gets the underlying geometry for this object.
        //     All rhino objects are composed of geometry and attributes.
        public virtual GeometryBase Geometry
        {
            get
            {
                if (m_edited_geometry != null)
                {
                    return m_edited_geometry;
                }

                if (m_original_geometry == null)
                {
                    IntPtr intPtr = UnsafeNativeMethods.CRhinoObject_Geometry(ConstPointer(), default(ComponentIndex));
                    if (IntPtr.Zero == intPtr)
                    {
                        return null;
                    }

                    m_original_geometry = GeometryBase.CreateGeometryHelper(intPtr, this);
                }

                return m_original_geometry;
            }
        }

Therefore, I don’t feel that any of these functions is actually thread safe when you are handling anything related to Objects that pertain to a document.

In most cases data is still residing in the unmanaged world, even if the pointer to that data isn’t directly known on first call. The data does not reside in managed world, most of the API is a wrapper for the unmanaged C++ SDK along with the data created for instances, either from document or free floating.

Accessing the document is not threadsafe, and to some extent different data containers aren’t necessarily threadsafe.

So indeed as a rule of thumb assume Rhino data isn’t thread safe.

Instead of doing multi-threaded operation on one object it is relatively safe to handle multiple objects in multiple threads. As long as document updates are done on the main thread.

It is probably more nuanced like that, I’ll let other devs chip in with their wisdom.

Thanks Nathan, thank you very much indeed. From my use case perspective, it doesn’t really work to do anything with objects that are in documents reliably in anything other than the main thread.

My code already failed when doing things that should be read only, such as trying to get list of objects that lie in different layers in parallel. I had issues just getting the objects, getting their geometry, accessing their attributes, etc.

I evolved my strategy now to launch my form in a totally separate thread. I save the dispatcher to the main rhino thread and use it whenever my UI or any background task it is doing requires interface with document objects.

Thanks, and have a fantastic day!