CrystalReportViewer.RefreshReport在BackgroundWorker中运行时挂起问题
问题根因
你遇到的死锁问题核心是所有Windows窗体控件、UI元素的操作必须在UI线程执行,而你把ShowReport()方法、frmReportPreview窗体创建、CrystalReportViewer控件操作的逻辑都放在了BackgroundWorker的DoWork回调中,该回调运行在独立工作线程,跨线程操作UI会导致线程阻塞、消息循环卡死,最终触发ContextSwitchDeadlock异常。
解决方案
调整逻辑边界:DoWork中仅执行报表模板加载、数据源绑定、数据计算等纯非UI操作,所有窗体创建、控件赋值逻辑全部移到RunWorkerCompleted回调中执行,该回调默认运行在UI线程,不会出现跨线程问题。
调整后dlgReportLoading代码
Imports System.Windows.Forms Public Class dlgReportLoading Private DisplayReport As Common.CRReport Private WithEvents LoadReportWorker As System.ComponentModel.BackgroundWorker ' 存储后台生成好的报表文档对象 Private _generatedReport As CrystalDecisions.CrystalReports.Engine.ReportDocument ' 存储当前操作类型 Private _operationType As Common.CRReport.GenerateReportOption Public Sub New(ByRef Report As Common.CRReport) InitializeComponent() DisplayReport = Report _operationType = Report.ReportOption 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 _operationType = Common.CRReport.GenerateReportOption.None Then Exit Sub ' 后台仅执行报表数据准备、模板加载等非UI操作 DisplayReport.PrepareReport() Select Case _operationType Case Common.CRReport.GenerateReportOption.DisplayOnScreen ' 只返回准备好的报表对象,不操作UI _generatedReport = DisplayReport.GetPreparedReportDocument() Case Common.CRReport.GenerateReportOption.SendToPrinter DisplayReport.PrintReport() Case Common.CRReport.GenerateReportOption.ExportToFile DisplayReport.ExportReport() End Select DisplayReport.ReportOption = Common.CRReport.GenerateReportOption.None End Sub Private Sub LoadReport_Complete(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LoadReportWorker.RunWorkerCompleted ' UI操作全部放在UI线程的回调中执行 If _operationType = Common.CRReport.GenerateReportOption.DisplayOnScreen AndAlso _generatedReport IsNot Nothing Then Dim ReportViewer As New frmReportPreview(_generatedReport) With ReportViewer .WindowState = FormWindowState.Maximized .Show() End With End If ' 关闭加载屏 Me.DialogResult = DialogResult.OK Me.Close() End Sub End Class
补充CRReport类方法
在Common.CRReport类中新增方法,返回已经准备好的报表文档对象:
Protected Friend Function GetPreparedReportDocument() As CrystalDecisions.CrystalReports.Engine.ReportDocument ' 返回你PrepareReport方法中生成的ReportDocument实例即可 Return Me.CrystalReport End Function
调整后frmReportPreview代码
移除原来的跨线程操作风险,直接接收准备好的报表对象,删掉Shown事件中的多余刷新逻辑:
Imports System.ComponentModel Public Class frmReportPreview Private ReportToDisplay As CrystalDecisions.CrystalReports.Engine.ReportDocument Public Sub New(ByVal report As CrystalDecisions.CrystalReports.Engine.ReportDocument) InitializeComponent() ReportToDisplay = report Me.rptViewer.ReportSource = ReportToDisplay Me.rptViewer.Zoom(1) End Sub Private Sub frmReportPreview_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing ReportToDisplay.Dispose() Me.rptViewer.ReportSource = Nothing End Sub End Class
其他说明
如果后续想用Async/Await方案,逻辑和上述调整完全一致:Await块中仅执行非UI的报表准备逻辑,后续UI操作都放在Await之后执行即可,代码会更简洁。
内容的提问来源于stack exchange,提问作者G_Hosa_Phat
相关产品推荐
相关产品推荐

