Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

I'm trying to run some operations (functions) in seperate threads so that the UI is not blocked. One of this operation is the generation of a feature class copy from an existing feature class into a temporary in memory feature class (used for processing later).

In cases where you must pass ArcObjects components from the main thread into a worker thread, serialize the object into a string, pass the string to the target thread, and deserialize the object back.

I'm using XMLPersistedObject to pass an serialized IName object to the copy function. The FeatureClass is then opened via IName.Open() and copied (structure + rows) into a temporary Workspace (created via the InMemoryWorkspaceFactory). The copied FeatureClass is then serialized back into an IName object again an returned to the main thread.

There IName.Open() fails with a generic COM Error (-2147467259).

The whole thing works when I use a different Workspace (e.g. ScratchWorkspaceFactory), but the copy process (see code below - maybe this can be improved?) and subsequent operations take longer than.

So, is there a possibility to share temporary FeatureClasses between Threads?

public class DataManagement
{
    #region Public Methods and Operators

    public static string CreateAndCopyToTemporaryFeatureWorkspace(string name)
    {
        IFeatureClass featureClass = ArcObjectHelper.CreateNameFromXML(name).Open();

        return ((IDataset)CreateAndCopyToTemporaryFeatureWorkspace(featureClass)).FullName.ToXMLString();
    }

    #endregion

    #region Methods

    private static IFeatureClass CreateAndCopyToTemporaryFeatureWorkspace(IFeatureClass featureClass)
    {
        IFeatureClass localFeatureClass = featureClass;

        // Copy FeatureClass structure
        IFeatureClass featureClassNew = CreateTemporaryFeatureClassCopy(localFeatureClass);

        // Copy rows
        using (ComReleaser comReleaser = new ComReleaser())
        {
            IFeatureCursor featureCursor = localFeatureClass.Search(null, false);
            comReleaser.ManageLifetime(featureCursor);

            IFeature feature;

            IFeatureBuffer featureBuffer = featureClassNew.CreateFeatureBuffer();
            comReleaser.ManageLifetime(featureBuffer);

            IFeatureCursor insertCursor = featureClassNew.Insert(true);
            comReleaser.ManageLifetime(insertCursor);

            while ((feature = featureCursor.NextFeature()) != null)
            {
                featureBuffer.Shape = feature.Shape;

                for (int i = 0; i < localFeatureClass.Fields.FieldCount; i++)
                {
                    if (localFeatureClass.Fields.Field[i].Type == esriFieldType.esriFieldTypeOID)
                    {
                        continue;
                    }

                    featureBuffer.Value[i] = feature.Value[i];
                }

                insertCursor.InsertFeature(featureBuffer);
            }

            featureCursor.Flush();
        }

        return featureClassNew;
    }

    private static IFeatureClass CreateTemporaryFeatureClassCopy(IFeatureClass featureClass)
    {
        IWorkspaceFactory workspaceFactory = new InMemoryWorkspaceFactory();
        IFeatureWorkspace featureWorkspace = ((IName)workspaceFactory.Create("", "InMemoryWorkspace", null, 0)).Open();

        IFields fields = new Fields();
        IFieldsEdit fieldsEdit = (IFieldsEdit)fields;
        fieldsEdit.FieldCount_2 = featureClass.Fields.FieldCount;

        for (int i = 0; i < featureClass.Fields.FieldCount; i++)
        {
            if (featureClass.Fields.Field[i].Type == esriFieldType.esriFieldTypeOID)
            {
                IField field = new Field();
                IFieldEdit fieldEdit = (IFieldEdit)field;
                fieldEdit.Name_2 = "ObjectID";
                fieldEdit.Type_2 = esriFieldType.esriFieldTypeOID;
                fieldsEdit.Field_2[i] = field;
            }
            else
            {
                IClone clone = (IClone)featureClass.Fields.Field[i];
                IField clonedField = (IField)clone.Clone();
                fieldsEdit.Field_2[i] = clonedField;
            }
        }

        IUID uid = featureClass.CLSID;

        Random random = new Random((int)DateTime.Now.Ticks);

        return featureWorkspace.CreateFeatureClass(random.Next(1000000, 10000000).ToString(CultureInfo.InvariantCulture),
            fields, (UID)uid, null, esriFeatureType.esriFTSimple, "shape", "");
    }

    #endregion
}
share|improve this question
    
Not sure but I don't think in-memory feature class names can start with a number. –  blah238 Mar 17 '13 at 19:34
    
Did you try using activator.CreateInstance instead of new ? See discussion here. Also threads in isolation discussion here. –  Kirk Kuykendall Mar 18 '13 at 3:25
    
Using activater.CreateInstance doesn't change anything. "An InMemoryWorkspaceFactory is used to connect to a temporary workspace which is stored in memory. These workspaces are commonly used to hold the results of an analysis operation or to hold objects in memory before persisting them to disk. When the last reference to the workspace is released, the workspace is destroyed and the memory released." - guess the same happens to temporary FeatureClasses. –  Dresel Mar 18 '13 at 16:45
    
All arcobjects (as at 9.3.1) are STA (single thread) objects. You can use them on a separate thread but they must stay on the thread that they are created from and can't be passed between threads. Look up IXMLSerialize to pass objects over threads by converting to and from XML. –  Michael Miles-Stimson Oct 9 '13 at 2:45
    
"I'm using XMLPersistedObject to pass an serialized IName object to the copy function. The FeatureClass is then opened via IName.Open() and copied (structure + rows) into a temporary Workspace (created via the InMemoryWorkspaceFactory). The copied FeatureClass is then serialized back into an IName object again an returned to the main thread." - I am already serializing objects. –  Dresel Oct 9 '13 at 9:08

1 Answer 1

up vote 1 down vote accepted

It is very unlikely that the in memory workspace is implemented in similar fashion to an on-disk one. Instead of having a persistent storage with IWorkspace, IFeatureClass etc mapped to on-disk structures IName.Open creating new ArcObjects that map to them, they will just be implementations in memory. IName.Open could just return a reference to the existing IWorkspace - which is an STA object that in your scenario was created on a different thread. This also hugely simplifies the implementation as there's no need for locking or similar as the in memory workspace can only be accessed by one thread.

I see two options for having this work - first, as Michael suggests, use a RAM drive or similar in-memory storage to create an on-disk workspace. The ram disk is likely to be an operating system level thing, though, so will persist between program runs - remember to clean it up.

The other option would be to keep the thread that creates the in-memory workspace alive and marshal calls to the workspace through that thread. The simplest way to do that is to use an IDispatcher (from WPF) created on the thread to Invoke (alternatively a Windows Forms Control acts as an IDispatcher). Depending on what you require from the workspace, you may get better design using other cross thread techniques e.g. custom message queues, awaitable collections.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.