ThinkGeo.com    |     Documentation    |     Premium Support

Handle Count is increasing

Hi Domenico,

This is by design. TaskCanceledException is thrown when MapView.ZoomToAsync is canceled.

It can be canceled in any of these cases:

  1. User interaction interrupts the animation (pan, mouse-wheel zoom, etc).
  2. Another programmatic change starts (e.g., a second ZoomToAsync, or RefreshAsync).
  3. mapView.CancellationTokenSource.Cancel() is called.

Other async APIs may swallow the cancellation, but ZoomToAsync intentionally throws so callers can tell the map status (CurrentScale, CenterPoint, etc) exactly when the map is being canceled.

You can just eat the exception if you don’t need it.

try
{
    await mapView.ZoomToAsync(targetScale, cancellationToken);
}
catch (TaskCanceledException)
{
    // Expected if the zoom was interrupted by user input or another request.
    // Safe to ignore (map remains valid). 
}

Thanks,
Ben

After upgraded our application to ThinkGeo 14.3.2 I am observing the cancellation exception as mentioned before. In some cases, the UI is not updated correctly when the cancellation operation is thrown.

For example, in our application the user can press the zoom buttons indicated by the red arrow.

In some cases when the exception is thrown then the UI panting operation seems to be stopped.

In the following log the line containing “Zoom the map to the selected Scale” is written just before the ZoomToAsync is called. From the timestamp in the log there is a time difference between the 2 calls of about 1 second (the user press 2 times on the button).

I 2025-08-15 13:52:33.276Z Damm.MapCtrl.WPF.View: Zoom the map to the selected Scale [Requested Scale: 7500][Corrected Scale: 7500][Current Extent: 9.794884334932853,54.920608499413156,9.817885628262747,54.90964359715807][Current Center: 9.8063849815978,54.915126048285615,0][Reason: ViewModel.RequestChangeCurrentScale]
I 2025-08-15 13:52:34.195Z Damm.MapCtrl.WPF.View: Zoom the map to the selected Scale [Requested Scale: 10000][Corrected Scale: 10000][Current Extent: 9.789134011600398,54.92334972497692,9.823635951595202,54.906902371594306][Current Center: 9.8063849815978,54.915126048285615,0][Reason: ViewModel.RequestChangeCurrentScale]
Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll
W 2025-08-15 13:52:34.462Z Damm.MapCtrl.WPF.View: Error setting the Scale [Scale: 10000][Reason: ViewModel.RequestChangeCurrentScale]
EXCEPTION: A task was canceled.
   at ThinkGeo.UI.Wpf.MapViewBase.REQ=(PointShape fromCenter, PointShape toCenter, Double fromResolution, Double toResolution, Double fromAngle, Double toAngle, UInt32 animationLength, EasingFunctionBase easing, Boolean fromMouseInteraction, CancellationToken cancellationToken)
   at ThinkGeo.UI.Wpf.MapViewBase.cEQ=(PointShape targetCenter, Double targetScale, Double rotationAngle, MapAnimationSettings animationSettings, CancellationToken cancellationToken, Boolean fromMouseInteraction)
   at ThinkGeo.UI.Wpf.MapViewBase.ZoomToAsync(Double targetScale, CancellationToken cancellationToken)
   at Damm.MapCtrl.WPF.MapView.SetScaleAsync(Double _scale, Boolean _isInitialScale, Boolean _doCorrectScale, String _reason, PointShape _centerAt, CancellationToken _cancellation) in D:\Repos\Releases\Ver08_12\Damm\DammMapCtrl.WPF\MapView.xaml.cs:line 4843
END EXCEPTION --------------------------------------------------------------------

Code:
DammLogger.Log(DammLoggerLevel.Info, TAG,
"Zoom the map to the selected Scale " +
“[Requested Scale: {0}]” +
“[Corrected Scale: {1}]” +
“[Current Extent: {2}][Current Center: {3}]” +
“[Reason: {4}]”,
_scale, correctedScale,
currenExtent, currenExtent.GetCenterPoint(),
_reason
);
await Map.ZoomToAsync(correctedScale, _cancellation);

Do you have any suggestion about how we can avoid the bad refreshing in the UI ?

Best regards
Domenico

Hi Ben

I made an example about the task cancellation problem I am facing.
As mentioned the map rendeing is an un-terminated state when this happen.
In the demo I reconfigure the scale when the windows change the size.
Sometimes the exception is thrown when the scale is reconfigued due to the windows is maximized.
DemoMapZoom_2025_08_18.zip (16.9 KB)
Note I have removed the raster image from the project.
Can you suggest me what I am doing wrong when I invoke the Map.ScaleToAsync.
Thanks

Best regards
Domenico

Hi Domenico,

The last line in your UpdateNodePosition(), can you use m_OverlayNodes.RefreshAsync(); instead?

Reason: mapView.RefreshAsync() cancels any in-flight rendering . If UpdateNodePosition() fires while the background is still drawing, that global cancel makes the background appear half-rendered—exactly what you’re seeing.
m_OverlayNodes.RefreshAsync() only repaints that overlay and doesn’t cancel other rendering, so it’s lighter and avoids interrupting the base map.

Two extra tips:

  • The RefreshAsync() is not awaited (because it’s kicked off by a timer) so multiple refresh might happen at the same time.
  • Please play with FeatureLayerWpfDrawingOverlay (mentioned earlier in this thread) when having a chance, which works better for this scenario

Thanks,
Ben

Hi Ben

Thanks for your reply.

I think I have found a solution for my “cancellation exception”.

I implemented the suggestions from your last post, but they did help.

Then I selected a different approach. I am now using a Boolean controlling if the Map operation is still running before to call a new one.

For example, this is my case where I scale the map on a view resize action. In my SizeChanged event handler I invoked ZoomToAsync.

I believe that the ZoomTo calculation is very heavy and slow, and it is performed on a different thread than the UI in ThinkGeo core. Therefore, I am able to get multiple UI events before the previous started operation was terminated (awaiter invoked). In this way invoking a new ZoomToAsync raised the cancellation exception.

Using a Boolean controlling if the previous event handler is terminated then I can prevent multiple invocations. I can just use a Boolean in the event handler code since it is executed in the UI thread.

Best regards

Domenico

Hi Domenico,

Nice find! Here’s a tighter rundown of ZoomToAsync and the knobs you can tweak:

  1. What happens under the hood

    • Animation: moves the view (extent/center) on the UI thread.
    • Rendering: draws the map on a worker thread, then composites the bitmap back to the UI.
  2. Duration / turning animation off

    • Default duration is ~170 ms.

    • You can override per call or globally:

      // Per call
      var anim = new MapAnimationSettings { Duration = TimeSpan.FromMilliseconds(300) };
      await mapView.ZoomToAsync(..., anim);
      
      // Globally (affects double-tap, mouse wheel, etc. Set it to 0 to avoid any animation)
      MapView.DefaultAnimationSettings = new MapAnimationSettings {
          Duration = TimeSpan.Zero   // no animation
      };
      
  3. When to draw: before vs during the animation

    • Default is DrawAfterAnimation (animate first, then render).

    • Switch to DrawWithAnimation to render while animating:

      var anim = new MapAnimationSettings {
          Duration = TimeSpan.FromMilliseconds(300),
          Type = MapAnimationType.DrawWithAnimation
      };
      await mapView.ZoomToAsync(..., anim);
      
    • There are animation progress events you can hook if you need status updates.

For a concrete reference, the HowDoI → Map Navigation → VehicleNavigation sample shows these pieces in action.

Thanks,
Ben

Hi Ben

Thanks for the extra information.

At the moment I have disabled the animation in our application since we do not need that.

I am also serializing the calls to the map async methods (like refresh, zoom and center) in the application in order to be sure that they do not get aborted due to a long running previous async/await call.

Do you have any estimate when you are ready with the fix for the increasing handle count in the ThinkGeo 14.3.2 you mentioned in a previous post ?

Best regards
Domenico

Hi Domenico,

We are still working on the handle count increasing issue. It’s time consuming to test after every change, and takes longer than we expected, let us give you an update mid-next week.

Thanks,
Ben

Hi Ben

I have updated our application with ThinkGeo 14.3.2 and I have serialized the calls to zoom, center, or refresh (only 1 call per time) in order to avoid the cancellation exception but I am facing 2 new problems with 14.3.2.

When the MAP is shown then our application process is taking long time to terminate on close (about 10 seconds). This has been observed both when running in DEBUG from Visual Studio (Diagnostic Tools) and when running in release build (Windows Task Manager).
The same behaviour can be observed also in the Demo app I uploaded in a previous post.
In both applications I have disabled the animation:

private void Map_Loaded(object sender, RoutedEventArgs e)
{
    Map.DefaultAnimationSettings = new MapAnimationSettings {
        Duration = 0,   // no animation
    };

In our main application the MAP is shown in its own Window furthermore the application creates also other child windows in order to presents other information to the user.
If a child window is in front of the MAP view and the mouse moves over the MAP then the MAP is moving in front, and it is stealing the focus.

Do you have any suggestion how to solve the problems ?
Thanks

Best regards
Domenico

Hi Domenico,

1, I tried both DemoMapZoom_2025_08_18.zip and DemoRasterMap_2025_08_12.zip, but couldn’t create it. Can you send me a demo to recreate it?

  1. Can you try this to the Form code with MapView?

this.Activated += MainWindow_Activated;
this.Deactivated += MainWindow_Deactivated;

    private void MainWindow_Deactivated(object? sender, EventArgs e)
    {
        mapView.IsHitTestVisible = false;
    }

    private void MainWindow_Activated(object? sender, EventArgs e)
    {
        mapView.IsHitTestVisible = true;
    }

Also, would you mind creating a new post for a new topic next time? That way it’ll be easier to keep track in the future.

Thanks,
Ben

Hi Ben

You have right. It was not nice mixing everything in the same post. I have created 2 new posts for the 2 problems I have reported:
Focus problem: Map stealing focus from child windows on mouse over - ThinkGeo UI for Desktop / WPF - ThinkGeo Discussion Forums

Delayed process termination: Map is delaying the process to close - ThinkGeo UI for Desktop / WPF - ThinkGeo Discussion Forums

I beging to get pressure about an hotfix for the Handle Count problem. How is going with this issue ?

Of course, I expect that the Stealing Focus problem could also become an issue at the customer. Therefore, my application release could also be constraint by a solution for this problem.

Best regards
Domenico

Hi Domenico,

Thanks for creating separate posts for different topics — it really helps with tracking issues more easily in the future.

We’ve identified the main cause of the handle count increase. When refreshing a LayerOverlay , the layer is drawn to an image on a background thread using Task.Run() , and then posted back to the map on the UI thread. We discovered that simply awaiting Task.Run will cause the handle count to grow. For example, you can reproduce this behavior by placing the following line in a Timer.Tick event:

await Task.Run(() => { });

This isn’t a true leak, but rather a case where the GC cannot keep up with releasing the underlying WaitHandle objects, so it appears like a leak.

We are still reviewing other leaks, but so far the only confirmed real handle leak we found was from CancellationTokenSource . That issue was fixed in v14.3.2 .

For your specific scenario, I recommend switching to FeatureLayerWpfDrawingOverlay , which is designed for dynamic layers refreshed in a timer:

  1. It doesn’t use Task.Run() — all drawing happens on the UI thread with hardware acceleration .
  2. It’s more suitable for incremental updates: instead of creating a large image each refresh, you can update just the relevant point, resulting in better memory usage.

Earlier you mentioned “causing the application to crash after some days run.” I’m curious if you’ve seen that issue again after updating to v14.3.2 with that CancellationTokenSource leak resolved? Regardless, moving to FeatureLayerWpfDrawingOverlay should be a better choice.

Thanks,
Ben

Hi Ben

Thanks for your explanation.

You wrote: “the GC cannot keep up with releasing the underlying WaitHandle objects”

I do no understand what you mean. Do you mean that it is a bug in .NET ?

Note that in our cases the application was running for 2~3 days and the handle count was over 10000 where 9000 alone was of kind “Event Handler” (from Sysinternals Handle tool).

About replacing LayerOverlay with FeatureLayerWpfDrawingOverlay it is my understanding that it is not possible for me to replace LayerOverlay with FeatureLayerWpfDrawingOverlay for all the cases

Our application uses 6 overlays both because it is requested by the API and also in order to hide/show or refresh a single overlay:

  • An overlay for moving devices
  • An overlay for (semi) static devices like base stations
  • An overlay for geofencing areas
  • An overlay ESRI Shapefiles
  • An overlay for the Graticule Grid
  • An overlay for the base map (Raster, OpenStreetMaps, WMS, …)

At the moment cases 1 to 3 are now using FeatureLayerWpfDrawingOverlay as you suggested. But this is not possible in cases 4 to 6.

OpenStreet maps and WMS maps use their own overlay: WmsOverlay and OpenStreetMapOverlay.

Raster maps (ECW and GeoTiff) are loaded using a GdalRasterLayer and they are added to a LayerOverlay. It is not possible to use FeatureLayerWpfDrawingOverlay in this case.

GraticuleFeatureLayer is raising an exception when used with FeatureLayerWpfDrawingOverlay therefore I am still using LayerOverlay: System.ArgumentOutOfRangeException: The input double value is out of range. (Parameter ‘distance’)

ShapeFileFeatureLayer is also raising an exception when used with FeatureLayerWpfDrawingOverlay therefore I am still using LayerOverlay: The FeatureSource is not open. Please call the Open method before calling this method.

We have just terminated the migration from version 14.6.2 to 14.3.2 and we have performed some quick test in order to check if we cover the previous functionality. We will start long run and handle count test now, but it is my impression that the handle count is still increasing.

I will start 3 long run tests in parallel:

  • Our application with the map view open
  • Our application without the map view open
  • The demo application (the source code is in this thread)

I will collect the handle count on all the 3 cases, and I will report the result in this thread

One last comment. We have observed this in the demo for the case: Map is delaying the process to close - ThinkGeo UI for Desktop / WPF - ThinkGeo Discussion Forums

This entry could be related to this one. We have observed very often that when the application terminates (with a long delay) the last line in Visual Studio Output Window is:

The program ‘[33604] DemoMapStealingFocus.exe’ has exited with code 3221225477 (0xc0000005) ‘Access violation’.

This is also reported in that thread

Best regards

Domenico

Hi Domenico,

We found the simple code as following, run it for a couple hours and the handle count increases. Also in our test the handles don’t drop even after calling GC.Collect(). Please have a try if you are interested.

var timer = new System.Threading.Timer(async _ =>
{
await Task.Run(() => { });
}, null, 0, 1000);

About overlays: each overlay comes with overhead in terms of memory, drawing, and synchronization. To optimize, we recommend consolidating where possible:

  1. Background overlay — keep your base map here.
  2. Static layers — combine ShapeFiles, static stations, and geofencing areas into one TileOverlay (with multi-tiles enabled). Enable tile caching so they do not redraw unnecessarily.
  3. Dynamic layers — use FeatureLayerWpfDrawingOverlay for frequently changing content. This avoids creating large images repeatedly and keeps memory usage lower.

When refreshing, it’s best to avoid calling Map.RefreshAsync() or StaticOverlay.RefreshAsync() since these force overlays to redraw. You can call WpfDrawingOverlay.RefreshAsync() for the dynamic layers. For navigation, use Map.ZoomToAsync() to adjust the extent without redrawing everything.

Thanks,
Ben

Hi Ben

I will just inform you that we had our application running during the weekend and receiving new position on the map without increasing the handle count.

The application was upgraded to ThinkGeo 14.3.2 with the overlay layout as I described in my last post.

We will start tests with user actions (zooming, panning, adding/removing shape files) and measure again the handle count

Best regards
Domenico

That’s great, please keep us posted!

Hi Ben

In a previous post you mentioned that you were able to see a handle count increasing which you point back to Task.Run (which is used when refreshing LayerOverlay).

Where you wrote:

“This isn’t a true leak, but rather a case where the GC cannot keep up with releasing the underlying WaitHandle objects, so it appears like a leak.”

What is ThinkGeo plan? Are you going to perform any action on this topic?

In the meantime, we are still monitoring the handle count in our updated application using ThinkGeo 14.3.2 and your suggested changes.

We would like also to include our customer which experienced the handle count issue, but we are waiting a solution for the crash (0xc0000374 and 0xc0000005) observed when using ECW with ThinkGeo version 14.3.2 (discussion: Map is delaying the process to close - ThinkGeo UI for Desktop / WPF - ThinkGeo Discussion Forums)

Best regards

Domenico

Hi Domenico,

If even the following code shows an increasing handle count, that suggests the way we are currently measuring handle usage may not be accurate, or that’s not a real handle leak

var timer = new System.Threading.Timer(async _ =>
{
    await Task.Run(() => { });
}, null, 0, 1000);

Did you have a chance to run this test on your side and see if the handle count still keeps increasing?

You mentioned earlier that “we had our application running during the weekend and receiving new positions on the map without increasing the handle count.” Does that mean you are now using a different way to measure handle usage? If so, could you share the method with us?

Our plan is to first establish a reliable way to test and verify whether there is a real handle leak. Once we have that, we can investigate further.

Regarding your other post: Map is delaying the process to close, my understanding was that this only happens when closing the application and has nothing to do with the handle leak. Please let me know if I misunderstood.

Thanks,
Ben

Hi Ben

I did not try the Task.Run sampe you sent. I trust in what you say.

I am measuring the handle count always with PerfMonitor from Windows.

Before upgrading to 14.3.2 the handle count in our application was increasing by letting the application running without user actions (only presenting the received positions updates).

After we upgrade to 14.3.2 and performed the suggested changes the same test does not present the increasing handle count.

From my point of view at the moment it seems that the handle count issue is solved by those changes and therefore this case is closed. We will try to load tests with user actions and if we get a different result then I will re-open this case.

Regarding the other post, sorry I mentioned in this post. My reason is that introducing the suggested changes for the Handle count issue has introduced a new problem in the application. I will continue this in the other thread.

Best regards

Domenico

Hi Domenico,

That makes sense. We’ve been able to narrow it down — await Task.Run(() => { }); is what was driving the handle count increase. With your change (especially switching to FeatureLayerWpfDrawingOverlay for the GPS points), that code path is no longer called, which is why the handle count stays stable.

We’ll continue looking into why that specific line causes a leak, but from a user’s perspective, continuously refreshing a single tile overlay (like for GPS updates) isn’t recommended anyway. Your current approach avoids that pattern and should be more efficient.

Thanks,
Ben