ThinkGeo.com    |     Documentation    |     Premium Support

Crash in ThinkGeo.MapSuite.Wpf.dll

Hello,

When moderately stressing some GIS code I have written (map display requiring refresh once every two seconds) I get the following crash:

System.Transactions Critical: 0 : <TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Critical"><TraceIdentifier>http://msdn.microsoft.com/TraceCodes/System/ActivityTracing/2004/07/Reliability/Exception/Unhandled</TraceIdentifier><Description>Unhandled exception</Description><AppDomain>QuickStartSample.exe</AppDomain><Exception><ExceptionType>System.InvalidOperationException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>Collection was modified; enumeration operation may not execute.</Message><StackTrace>   at ThinkGeo.MapSuite.Wpf.Tile.DrawException(GeoCanvas geoCanvas, Exception exception)

at ThinkGeo.MapSuite.Wpf.Tile.Draw(GeoCanvas geoCanvas)
at ThinkGeo.MapSuite.Wpf.Tile.iFY=(Object status)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at ThinkGeo.MapSuite.Wpf.Tile.DrawException(GeoCanvas geoCanvas, Exception exception)
at ThinkGeo.MapSuite.Wpf.Tile.Draw(GeoCanvas geoCanvas)
at ThinkGeo.MapSuite.Wpf.Tile.iFY=(Object status)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
An unhandled exception of type ‘System.InvalidOperationException’ occurred in ThinkGeo.MapSuite.Wpf.dll
Collection was modified; enumeration operation may not execute.

I was wondering whether anyone else has experienced similar and whether they could point me to the root cause.I am unable to debug the problem as none of my own code is loaded when the crash occurs.

Many thanks,

Dan.

Hi Dan,

The Collection was modified is a general exception, we also hadn’t found more information from your exception call stack.

And I think if you can provide more detail information about it is helpful. You can let us know your dll version, related code or the steps to reproduce it. A sample which can reproduce it should be better.

Regards,

Ethan

Hi Ethan,

Thanks for getting back to me. Here is some of the code that gets called regularly during the stress test:

A function called TxChange:

        private void TxChange(TxEntry tx_entry)
    {
        try
        {
            TxDelete(tx_entry);
            TxAdd(tx_entry);
        }
        catch (Exception e)
        {
            throw;
        }
    }

This just calls TxDelete:

        private void TxDelete(TxEntry tx_entry)
    {
        if (tx_selected_layer.InternalFeatures.Count > 0)
            tx_selected_layer.InternalFeatures.Remove(tx_entry.Id.ToString());
        if (tx_unselected_layer.InternalFeatures.Count > 0)
            tx_unselected_layer.InternalFeatures.Remove(tx_entry.Id.ToString());
        if (tx_label_overlay.EditShapesLayer.InternalFeatures.Count > 0)
            tx_label_overlay.EditShapesLayer.InternalFeatures.Remove(tx_entry.Id.ToString());
    }

Followed by TxAdd:

        private void TxAdd(TxEntry tx_entry)
    {
        MapViewModel map_view_model = (MapViewModel)DataContext;
        RxEntry rx_entry = map_view_model.GetRxEntry(tx_entry.Rx);
        double bearing = tx_entry.Bearing;
        if (map_view_model.absolute_bearing)
            bearing += rx_entry.Direction;
        double horiz_offset = map_view_model.radius * Math.Sin(bearing.ToRadians());
        double vert_offset = map_view_model.radius * Math.Cos(bearing.ToRadians());
        List<Vertex> vertices = new List<Vertex>();
        vertices.Add(new Vertex(rx_entry.Longitude, rx_entry.Latitude));
        vertices.Add(new Vertex(rx_entry.Longitude + horiz_offset, rx_entry.Latitude + vert_offset));
        LineShape line = new LineShape(vertices);
        line.Tag = tx_entry.Id;

        TextStyle text_style = new TextStyle("Label", new GeoFont("Arial", 10), new GeoSolidBrush(GeoColor.SimpleColors.Black));
        text_style.PointPlacement = PointPlacement.Center;
        ValueStyle value_style = new ValueStyle();
        value_style.ColumnName = "Label";
        value_style.ValueItems.Add(new ValueItem(tx_entry.Name, text_style));
        tx_label_overlay.EditShapesLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(value_style);
        tx_label_overlay.EditShapesLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

        ColourEntry(tx_entry, tx_selected_layer, tx_unselected_layer, tx_label_overlay, line, add_label: false);

        PointShape point = new PointShape(rx_entry.Longitude + horiz_offset, rx_entry.Latitude + vert_offset);
        ColourEntry(tx_entry, tx_selected_layer, tx_unselected_layer, tx_label_overlay, point, add_label: true);
    }

The ColourEntry function is as follows:

        private void ColourEntry<T>(Entry entry, InMemoryFeatureLayer layer_selected, InMemoryFeatureLayer layer_unselected, EditInteractiveOverlay label_overlay, T shape, bool add_label) where T : BaseShape
    {
        if (Map.MapUnit == GeographyUnit.Meter)
            shape = (T)proj4.ConvertToExternalProjection(shape);

        Feature feature = new Feature(shape);
        feature.ColumnValues["Label"] = entry.Name;

        if (entry.Selected)
        {
            if (!layer_selected.InternalFeatures.Contains(entry.Id.ToString()))
                layer_selected.InternalFeatures.Add(entry.Id.ToString(), feature);
            if (layer_unselected.InternalFeatures.Count > 0)
                layer_unselected.InternalFeatures.Remove(entry.Id.ToString());
            if (add_label && !label_overlay.EditShapesLayer.InternalFeatures.Contains(entry.Id.ToString()))
                label_overlay.EditShapesLayer.InternalFeatures.Add(entry.Id.ToString(), feature);
        }
        else
        {
            if (!layer_unselected.InternalFeatures.Contains(entry.Id.ToString()))
                layer_unselected.InternalFeatures.Add(entry.Id.ToString(), feature);
            if (layer_selected.InternalFeatures.Count > 0)
                layer_selected.InternalFeatures.Remove(entry.Id.ToString());
            if (add_label && label_overlay.EditShapesLayer.InternalFeatures.Contains(entry.Id.ToString()))
                label_overlay.EditShapesLayer.InternalFeatures.Remove(entry.Id.ToString());
        }
    }

If I put a breakpoint inside the catch in TxChange, it never triggers, but the code does break inside the Wpf dll with none of my own code loaded.

Many thanks for any insights you are able to provide,

Dan.

Hi Ethan,

I should have added that the TxChange function gets called by TxEntryChangedEventHandler:

        private void TxEntryChangedEventHandler(object sender, EntryChangedEventArgs<TxEntry> e)
    {
        Dispatcher.Invoke((Action)(() => { TxChange(e.entry); }));
        Refresh();
    }

The Refresh function is as follows:

        private void Refresh()
    {
        Dispatcher.Invoke((Action)(() => { Map.Refresh(); }));
    }

I think the crash is occurring inside the Map.Refresh function, inside the ThinkGeo dll. Do you think the use of two Dispatcher.Invoke functions (one to launch TxChange and one to launch Map.Refresh) is causing thread contention for a common resource?

As usual, up against the wire, so any thoughts from anyone would be greatly appreciated.

Dan.

Hi Ethan,

Indeed, commenting out the call to Refresh in TxEntryChangedEventHandler does prevent the crash and significantly reduces CPU loading. I wonder, what is the best policy for calling Map.Refresh?

Regards,

Dan.

Hi Dan,

Could you please let us know your more detail requirement about refresh the map?

If you refresh map and remove/add feature in different thread, it maybe cause the exception you mentioned, because the Foreach function need make sure the collection don’t changed, and in our refresh loop the features collection is necessary.

In fact we don’t always refresh map, the map will be refresh if we pan/zoom. If you need change the features, you can also call the refresh after this operation.

We don’t suggest you refresh map high frequency if nothing change in data or render style.

Regards,

Ethan

Hi Ethan,

The problem with Map.Refresh is still ocurring. Indeed, it caused the software to crash during a demo on Tuesday, to considerable embarrassment. I hope I am not going to regret my decision to use Map Suite!

I have tightened up how often the refresh gets called and been more specific about what gets refreshed. There are now two refresh functions as follows:

        private void RxRefresh()
    {
        List<Overlay> overlays = new List<Overlay>();
        overlays.Add(rx_overlay);
        overlays.Add(rx_label_overlay);
        Dispatcher.Invoke((Action)(() => { Map.Refresh(overlays); }));
    }

        private void TxRefresh()
    {
        List<Overlay> overlays = new List<Overlay>();
        overlays.Add(tx_overlay);
        overlays.Add(tx_label_overlay);
        Dispatcher.Invoke((Action)(() => { Map.Refresh(overlays); }));
    }

I use Dispatcher.Invoke exclusively to ensure synchronous processing (rather than the asynchronous Dispatcher.BeginInvoke) so I don’t think thread contention for the same resource should be an issue.Having said that, I can see that the Map.Refresh command spawns additional worker threads, which I have no control over, so maybe that is the problem? It would seem that Map.Refresh is not thread safe - why doesn’t it put a mutex on the resources it needs for the duration of its worker threads? Is it down to me to put a mutex on these resources?

Many thanks for any help you are able to provide. I am new to C#, WPF and MapSuite for WPF, so it has been quite a steep learning curve!

Dan.

Hi Dan,

Because performance reason, we cannot lock everything when map refresh, but we will lock all the necessary source for example the layers which are drawn. So we cannot imagine why the “Collection was modified” exception is thrown.

I think we need a sample to test and reproduce the exception, so our developer can looks into it and see where is the problem.

I don’t know what’s the scenario in your side(what’s the layers you are using, how you operate the map to reproduce the exception), so I only build a really simple sample, you can modify it to reproduce the exception and upload it here.

TestCollectionWasModified.zip (102.1 KB)

Any question please let us know.

Regards,

Ethan

Hi Ethan,

I have finally got round to modifying the simple example you provided to replicate the crash I have been experiencing in Map.Refresh. Please see the attached zip file for all the code and accompanying data sources. If you build and run the code as it stands, you will see randomly generated points and lines (with labels) appearing on the map. If you modify the code to reduce the refresh_delay from its current setting of 100 to, say, a value of 10, you will see the code immediately crash with the ‘collection was modified’ error. It seems that Map.Refresh returns when it still has worker threads active and a subsequent call to Map.Refresh causes thread contention. Putting a delay in ensures the worker threads have finished before the next call to Map.Refresh. In summary, the problem is a threading issue, but not in my code. I believe Map.Refresh should not return until its worker threads have finished. I would be grateful if you could pass this information on to the Tech Team for their appraisal and let me know whether they agree with my conclusion.

Many thanks,

Dan.

TestCollectionWasModified.zip (1.6 MB)

Hi Daniel,

Our default tile mode is MultipleTile, the refresh for it is asynchronous. Which means when we use MultipleTile mode, we cannot lock the layer or other object.

And that’s why you will met this problem, because the map is still read feature from InternalFeatures, your other code add/delete the feature in it.

We want to know more detail about your requirement, in fact if your requirement is just like GPS locate, you can use SingleTile mode for the special overlay, the performance should still be good enough for InmemoryFeatureLayer.

So the solution is, you can try to use SingleTile mode or leave enough time for refresh, the first one is better.

Regards,

Ethan