CrystalReportViewer.RefreshReport 在来自 BackgroundWorker 的 运行 时挂起

CrystalReportViewer.RefreshReport hangs when running from BackgroundWorker

我正在尝试通过在 Crystal 报告 prepared/loaded 期间添加加载屏幕来“增强”我的报告代码。在我开始尝试添加加载屏幕之前,我的所有报告都会正常显示,但光标的变化还不足以表明应用程序仍在拉取报告——其中一些可能需要一段时间 - 所以我想提供一个更“明显”的视觉提示。

为了实现这一点,我已将报告创建方法调用放入存在于加载屏幕本身的 BackgroundWorker 中(我还没有抽出时间学习如何使用 Async/Await 足够好,但可以放心使用 )。加载屏幕正确出现并且一切 看起来 都按预期工作,直到它实际尝试在屏幕上显示报告。此时,“正在处理文档,请稍候。”框出现(CrystalReportViewer 控件中,用于显示报告), 但它只是坐在那里,甚至没有旋转。最终,我的 IDE 抛出了关于接收 ContextSwitchDeadlock 的错误,我几乎只需要取消执行。

这是我的dlgReportLoading“启动画面”,带有一个包含动画 GIF 的 PictureBox 控件:

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

如上面代码所述,我目前正在测试此处定义的 ShowReport() 方法:

        Protected Friend Sub ShowReport()
            Dim ReportViewer As frmReportPreview

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

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

frmReportPreview 是这样的:

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

dlgReportLoading 表单正确弹出,动画播放直到 frmReportPreview 弹出在它前面(它不会关闭)。通常带有动画旋转圆圈的小框表示正在加载报告数据,但几乎立即冻结在原地。

在调用 ShowReport() 方法后,我的 dlgReportLoading 表单的 LoadReport_DoWork() 方法中有一个断点,但它永远不会到达那个点。我在那种形式的 LoadReport_Complete() 方法中也有一个,它永远不会命中任何一个,并且该对话框永远不会真正关闭。

我在 frmReportPreview_Shown 方法的末尾放置了另一个断点,就在 Me.rptViewer.RefreshReport() 调用之后,但它也没有命中,所以很明显这是卡住的地方, 但 仅当通过 BackgroundWorker 生成报告时 。如果我只是调用 ShowReport() 方法而不通过“启动画面”和 BackgroundWorker 发送它,一切都会正常生成和显示。

我已经尝试将 RefreshReport() 方法放入其 自己的 BackgroundWorker 中,但行为没有任何变化。我已经尝试使用 ShowDialog() 而不是仅 Show() 使 frmReportPreview 对象模态显示。 None 这似乎有助于解决问题。

我感觉有些东西在某个地方被过早地处理掉了,但我不知道那会是什么。如果需要,我可以提供来自 frmReportPreview 的其余报告准备代码,但据我所知,所有 似乎 都可以正常工作。我不反对尝试其他方法来实现在所有报告准备过程中向用户显示加载屏幕的目标 - 例如,Async/Await 或其他多线程方法 - 所以 欢迎任何的建议。如果需要任何其他说明,请告诉我。


环境

微软 Windows 10 Pro 21H1(OS 内部版本 19043.1348)
微软 Visual Studio 社区 2017 (v15.9.38)
Crystal .NET Framework v13.0.3500.0(运行时版本 2.0.50727)的报告


编辑: 我忘了提到整个混乱是从我的 CRReport class 中的 GenerateReport() 方法调用的,定义为:

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

反过来,从我的主窗体中调用它是这样的:

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

您应该将繁重的工作和 UI 操作分离到不同的方法中,以便将它们放入适当的 BackgroundWorker 事件中:

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

因为LoadReport_DoWork实际上运行在一个新的非UI线程上,而LoadReport_Complete运行在调用者线程上,它是一个UI线程。只有在那里您才能与 UI 交互并显示表单等