Home > Back-end >  CrystalReportViewer.RefreshReport hangs when running from BackgroundWorker
CrystalReportViewer.RefreshReport hangs when running from BackgroundWorker

Time:11-19

I'm trying to "enhance" my reporting code by adding a loading screen while the Crystal Report is being prepared/loaded. Before I started trying to add the loading screen, all of my reports would come up just fine, but the cursor change just wasn't "enough" of an indication that the application was still working on pulling the report - some of them can take a while - so I wanted to provide a more "obvious" visual cue.

In order to accomplish this, I've put the report creation method calls into a BackgroundWorker that exists in the loading screen itself (I haven't gotten around to learning how to use Async/Await well enough yet to feel comfortable using that instead). The loading screen comes up correctly and everything appears to work as expected until it actually attempts to display the report on screen. At that point, the "Please wait while the document is processing." box comes up (in the CrystalReportViewer control in the form used to display reports), but it just sits there, not even spinning. Eventually, my IDE throws an error about receiving a ContextSwitchDeadlock and I pretty much just have to cancel execution.

Here's my dlgReportLoading "splash screen" with a PictureBox control that contains an animated GIF:

Imports System.Windows.Forms

Public Class dlgReportLoading
    Private DisplayReport As Common.CRReport
    Private WithEvents LoadReportWorker As System.ComponentModel.BackgroundWorker

    Public Sub New(ByRef Report As Common.CRReport)
        InitializeComponent()
        DisplayReport = Report
    End Sub

    Private Sub dlgReportLoading_Load(sender As Object, e As EventArgs) Handles Me.Load
        Me.Cursor = Cursors.WaitCursor
        Me.TopMost = True
        Me.TopMost = False

        LoadReportWorker = New System.ComponentModel.BackgroundWorker
        LoadReportWorker.RunWorkerAsync()
    End Sub

    Private Sub dlgReportLoading_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
        Me.Cursor = Cursors.Default
    End Sub

    Private Sub LoadReport_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles LoadReportWorker.DoWork
        If Not DisplayReport.ReportOption = Common.CRReport.GenerateReportOption.None Then
            Select Case DisplayReport.ReportOption
                Case Common.CRReport.GenerateReportOption.DisplayOnScreen
                    '-- This is the method I'm currently testing
                    DisplayReport.ShowReport()
                Case Common.CRReport.GenerateReportOption.SendToPrinter
                    DisplayReport.PrintReport()
                Case Common.CRReport.GenerateReportOption.ExportToFile
                    DisplayReport.ExportReport()
            End Select
        End If

        DisplayReport.ReportOption = Common.CRReport.GenerateReportOption.None

        '--
        '-- This code was in use before trying to generate the reports in the background
        'If Not DisplayReport.CrystalReport Is Nothing Then
        '    DisplayReport.CrystalReport.Dispose()
        'End If
        '--
    End Sub

    Private Sub LoadReport_Complete(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LoadReportWorker.RunWorkerCompleted
        Me.DialogResult = DialogResult.OK
        Me.Close()
    End Sub
End Class

As noted in the code above, I'm currently testing the ShowReport() method as defined here:

        Protected Friend Sub ShowReport()
            Dim ReportViewer As frmReportPreview

            Me.PrepareReport()
            ReportViewer = New frmReportPreview(Me)

            With ReportViewer
                .WindowState = FormWindowState.Maximized
                .Show()
            End With
        End Sub

And the frmReportPreview is this:

Imports System.ComponentModel

Public Class frmReportPreview
    Private DisplayReport As Common.CRReport
    Private ReportToDisplay As CrystalDecisions.CrystalReports.Engine.ReportDocument

    Public Sub New(ByRef Report As Common.CRReport)
        InitializeComponent()

        DisplayReport = Report
        PrepareReportForDisplay()

        Me.rptViewer.ReportSource = Nothing
        Me.rptViewer.ReportSource = ReportToDisplay

        ' SET ZOOM LEVEL FOR DISPLAY:
        '    1 = Page Width
        '    2 = Whole Page
        '    25-100 = zoom %
        Me.rptViewer.Zoom(1)
        Me.rptViewer.Show()
    End Sub

    Private Sub frmReportPreview_Shown(sender As Object, e As EventArgs) Handles Me.Shown
        '-- HANGS HERE
        Me.rptViewer.RefreshReport()
    End Sub

    Private Sub frmReportPreview_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
        ReportToDisplay.Dispose()
        Me.rptViewer.ReportSource = Nothing
    End Sub
    
    '...CODE FOR PREPARING THE REPORT TO BE DISPLAYED
End Class

The dlgReportLoading form pops up correctly and the animation plays until the frmReportPreview pops up in front of it (it doesn't close). The little box that has what is normally an animated spinning circle indicating the report data is being loaded appears, but almost immediately freezes in place.

I have a breakpoint in the LoadReport_DoWork() method of my dlgReportLoading form after the call to the ShowReport() method, but it never gets to that point. I also have one in the LoadReport_Complete() method of that form that it never hits either and that dialog never actually closes.

I put another breakpoint at the end of the frmReportPreview_Shown method, right after the Me.rptViewer.RefreshReport() call, but it never hits that either, so it seems clear that this is where things are getting stuck, but only when the report is being generated through the BackgroundWorker. If I just call the ShowReport() method without sending it through the "splash screen" and BackgroundWorker, everything generates and displays normally.

I've tried putting the RefreshReport() method into its own BackgroundWorker with no change in the behavior. I've tried making the frmReportPreview object display modally with ShowDialog() instead of just Show(). None of this seems to help the issue.

I have a feeling something is being disposed of too early somewhere, but I can't figure out what that would be. I can provide the rest of the report preparation code from frmReportPreview if required, but that all seems to be working without error, as far as I can tell. I'm not averse to trying alternate methods of accomplishing my goal of showing the user a loading screen while all the report preparation is taking place - e.g., Async/Await or other multi-threading methods - so any suggestions are welcome. Please let me know if any additional clarification is needed.


ENVIRONMENT

Microsoft Windows 10 Pro 21H1 (OS build 19043.1348)
Microsoft Visual Studio Community 2017 (v15.9.38)
Crystal Reports for .NET Framework v13.0.3500.0 (Runtime version 2.0.50727)


EDIT: I forgot to mention that this whole mess is being called from a GenerateReport() method in my CRReport class defined as:

Public Sub GenerateReport(ByVal ReportGeneration As GenerateReportOption)
    Me.ReportOption = ReportGeneration

    If Me.ReportOption = GenerateReportOption.None Then
        '...CODE FOR REQUESTING A GENERATION OPTION FROM THE USER
    End If

    Dim ReportLoadingScreen As New dlgReportLoading(Me)

    ReportLoadingScreen.ShowDialog()
End Sub

Which, in turn, is being called from my main form like this:

Private Sub PrintMyXMLReport(ByVal XMLFile As IO.FileInfo)
    Dim MyXMLReport As New IO.FileInfo("\\SERVER\Applications\Reports\MyXMLReport.rpt")
    Dim Report As New Common.CRReport(MyXMLReport, XMLFile)
    
    Report.GenerateReport(Common.CRReport.GenerateReportOption.DisplayOnScreen)
End Sub

CodePudding user response:

You should separate the heavy lifting and UI operations into distinct methods in order to put them into the appropriate BackgroundWorker events:

Protected Friend Sub PrepareReport()
    ' perform long-running background work
End Sub

Protected Friend Sub ShowReport()
    Dim ReportViewer = New frmReportPreview(Me) With {.WindowState = FormWindowState.Maximized}
    ReportViewer.Show()
End Sub
Private DisplayReport As Common.CRReport

Private Sub LoadReport_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles LoadReportWorker.DoWork
    DisplayReport.PrepareReport()
End Sub

Private Sub LoadReport_Complete(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LoadReportWorker.RunWorkerCompleted
    DisplayReport.ShowReport()    
    Me.DialogResult = DialogResult.OK
    Me.Close()
End Sub

because LoadReport_DoWork actually runs on a new non-UI thread, and LoadReport_Complete runs on the caller thread, which is a UI thread. Only there can you interact with the UI and show Forms etc.

  • Related