on 08-15-2017 12:17 PM
Hello,
ReportAlbum instance is held by CommandManager and it cannot be collected by GC. See screenshot from .NET Memory Profiler.
I've analysed that the problem is ReportAlbum constructor that adds CommandBinding throught RegisterClassCommandBinding into CommandManager and passes instance method TabCloseExecuted.
Hi,
I post my comment as an answer.
Workaround to fix the memory leak:
After disposing CrystalReportsViewer
var reportAlbum = (ReportAlbum)this.crystalReportsViewer.ViewerCore.GetType()<br> .GetField("reportAlbum", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)<br> .GetValue(this.crystalReportsViewer.ViewerCore);<br> this.crystalReportsViewer.Dispose();<br> this.ViewerWorkaroundRemoveEventHandler(reportAlbum);
a I remove the CommandBinding and event handler.
private void ViewerWorkaroundRemoveEventHandler(ReportAlbum reportAlbum)
{
var handler = reportAlbum.GetType().GetMethod("TabCloseExecuted", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
var @delegate = Delegate.CreateDelegate(typeof(ExecutedRoutedEventHandler), reportAlbum, handler);
var field = typeof(CommandManager).GetField("_classCommandBindings", BindingFlags.NonPublic | BindingFlags.Static);
var classCommandBindings = (HybridDictionary)field.GetValue(null);
var bindings = classCommandBindings[typeof(TabItem)] as CommandBindingCollection;
if (bindings != null)
{
for (int i = 0; i < bindings.Count; i++)
{
var binding = bindings[i];
var ev = binding.GetType().GetEvent("Executed");
var evField = (Delegate)binding.GetType().GetField("Executed",
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.FlattenHierarchy |
BindingFlags.Static).GetValue(binding);
if (evField.Target == reportAlbum)
{
ev.RemoveEventHandler(binding, @delegate);
bindings.RemoveAt(i);
break;
}
}
}
}
Tomas
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi,
We have been trying to track down a memory leak in our WPF application and have eventually tracked it down to this exact issue reported by Tomas Homola (dotMemory gives the exact same reference paths). His suggested workaround also worked for us, although it was very unhelpfully hidden behind a "Show all" button at the bottom of the comments and I almost missed it! Even liking the comment does not seem to un-hide it.
It's hard to believe that we are more than 5 years and 10 service packs on from when this bug was first reported, plus another year since Tomas fed back his workaround and this is still not fixed.
Can this please be escalated to be fixed in SP 33?
Can the "Best Answer" also be changed to Tomas Homola's workaround in the meantime, so other people trying to find a solution to this bug can locate it easier?
Thanks,
Gareth
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi
I've added the workaround as answer.
Tomas
Hi Thomas,
Could you please briefly describe your approach? I have the same problem with memory leaks 😞
I use diagnostics tools in Visual Studio 2017. There I can see the memory leaks very well. Even in a very simple test application that only contains the CrystalReportsViewer.
Thank you in advance for your help!
Kind regards.
Andreas
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Andreas,
After disposing CrystalReportsViewer
var reportAlbum = (ReportAlbum)this.crystalReportsViewer.ViewerCore.GetType()
.GetField("reportAlbum", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
.GetValue(this.crystalReportsViewer.ViewerCore);
this.crystalReportsViewer.Dispose();
this.ViewerWorkaroundRemoveEventHandler(reportAlbum);
a I remove the CommandBinding and event handler.
private void ViewerWorkaroundRemoveEventHandler(ReportAlbum reportAlbum)
{
var handler = reportAlbum.GetType().GetMethod("TabCloseExecuted", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
var @delegate = Delegate.CreateDelegate(typeof(ExecutedRoutedEventHandler), reportAlbum, handler);
var field = typeof(CommandManager).GetField("_classCommandBindings", BindingFlags.NonPublic | BindingFlags.Static);
var classCommandBindings = (HybridDictionary)field.GetValue(null);
var bindings = classCommandBindings[typeof(TabItem)] as CommandBindingCollection;
if (bindings != null)
{
for (int i = 0; i < bindings.Count; i++)
{
var binding = bindings[i];
var ev = binding.GetType().GetEvent("Executed");
var evField = (Delegate)binding.GetType().GetField("Executed",
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.FlattenHierarchy |
BindingFlags.Static).GetValue(binding);
if (evField.Target == reportAlbum)
{
ev.RemoveEventHandler(binding, @delegate);
bindings.RemoveAt(i);
break;
}
}
}
}
Tomas
Hi Tomas,
thank you for the fast answer.
This is really interesting. I've just made an experiment with the test application. Your solution seems to be working correctly, the improvement is clearly visible (see screenshots in the attachment.)
Thanks!
Andreas
Screenshot 1. Without workaround.
Screenshot 2. With workaround.
Cool,
Just be aware if you run into resource issues I won't be able to escalate to DEV.
Also, I just escalated possibly a similar issue to DEV also, CPU seems to consume 1 ->5 % when the report is loaded, may be the same animation causing the problem.
Set for SP 22.
Don
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
The CR Engine will only allow 3 reports to be processed at any one instance, it will work up to 75 ish because not all reports are executed at the exact same time. So depending on the report load you may be able to possible get 75 jobs running at one time.
For high Report processing applications this is not the right way to use it. You will run into all sorts of resource issues and nothing we can do about it, it's by design.
CR for VS simply was not designed nor is it capable to be used in a high work load environment.
All I can suggest then is possibly don't use the WPF viewer and use the CR Windows Form viewer, it may help.
What you really need to use is CR Server or the full BOE Servers that can handle 1000's of reports in any given time depending on licensing and other options, assuming you give it enough resources and add multiple Report Servers you will be able to manage multiple reports.
Be aware each users instance of a report consumes one license.
Here's a link for more info in CR Server:
https://www.sap.com/products/crystal-server.html
It's a simplified version of the full BOE Product, limited to one PC but can have up to 4 Report Application Servers on it. RAS is running as a service and not an inproc version you may be/are using.
Don
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Tomas,
So all you are doing is loading 1000 instances of the WPF viewer... WHY, makes no sense to do that, you cannot load 1000 reports, engine won't allow it, 3 max.
Load a report, when done close the report and load a new one in the same instance of the viewer. The single instance leaks 316 bytes, nothing to be concerned about.
Don
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Don,
The engine allows me to have multiple viewers in same time. There is no limit.
We have users that browse many reports during a day and they require to see them in one time.
The leak is bigger because the CrystalReportsViewer is placed on a Window. This window contains more UI controls than single CrystalReportsViewer. Plus ViewModels...
Tomas
Where does it show that it's the WPF viewer that is the cause of the leak?
How much of a leak are you seeing?
Have you ruled out all of Microsoft's dll's don't leak?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Here is example:
<!-- MainWindow.xaml -->
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="OpenCrystal" Click="Button_Click" />
</Grid>
</Window>
/* MainWindow.xaml.cs */
using System;
using System.Windows;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 1000; i++)
{
var wnd = new CrystalReportsWindow();
wnd.Show();
wnd.Close();
}
}
}
}
<!-- CrystalReportsWindow.xaml -->
<Window
x:Class="WpfApp2.CrystalReportsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:crview="clr-namespace:SAPBusinessObjects.WPF.Viewer;assembly=SAPBusinessObjects.WPF.Viewer"
Title="CrystalReportsWindow" Height="600" Width="800">
<crview:CrystalReportsViewer x:Name="crystalReportsViewer" />
</Window>
/* CrystalReportsWindow.xaml.cs */
using System;
using System.Windows;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for CrystalReportsWindow.xaml
/// </summary>
public partial class CrystalReportsWindow : Window
{
public CrystalReportsWindow()
{
InitializeComponent();
}
}
}
The leak is everytime the SAPBusinessObjects.WPF.Viewer.CrystalReportsViewer is created. It prevents the closed CrystalReportsWindow to be collected.
The ReportAlbum instances are held by CommandManager.
After 1000 crystal reports viewer windows shown&closed this simple example takes 789464 KB of memory.
User | Count |
---|---|
72 | |
11 | |
10 | |
7 | |
6 | |
6 | |
6 | |
6 | |
5 | |
5 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.