C#:在ReportViewer中单击“刷新”按钮几次后应用程序崩溃

时间:2017-01-25 08:56:12

标签: c# winforms reportviewer

简介

这是ReportViewer在我的Winforms应用程序中的一部分,该应用程序的目标是.NET framework 4.6.1。

enter image description here

“确定”按钮调用btnOk_Click事件,而刷新按钮(圆圈中的双绿色箭头)调用ReportViewer事件,该事件本身使用空参数调用btnOK_Click事件。下面的代码说明了它

代码

private void btnOk_Click(object sender, EventArgs e)
    {
        try
        {
            ...
            //Code adding datasources to rpvCustomReport
            ...
            this.rpvCustomReport.RefreshReport(); //Causing the error.
            //NOTE: this.rpvCustomReport is instantiated in .Design.cs as
            //new Microsoft.Reporting.WinForms.ReportViewer();
            ...
        }
        catch (Exception ex)
        {
            HandleException(ex); //Custom function to handle exception
        }
    }

private void rpvCustomReport_ReportRefresh(object sender, CancelEventArgs e)
    {
        this.btnOk_Click(null, null); //sender object and event variables are not used in this function, hence, it is set to null
    }

问题

在ReportViewer中单击“刷新”按钮几次后,我的应用程序崩溃了。

enter image description here

这是我在事件查看器>中找到的内容。 Windows日志>应用

消息:在AsyncLocal通知回调中没有处理异常。我尝试使用Google搜索错误但是很短暂。

enter image description here

线索信息

  1. 调试模式(Visual Studio 2015)
  2. 中不会发生此问题
  3. 当我快速单击“确定”按钮时,不会发生此问题。
  4. 当我在MessageBox.Show()事件中 this.rpvCustomReport.RefreshReport();行之后添加冗余/测试代码(例如,btnOk_Click时,不会发生此问题。但是当我在那行之前添加时,就会出现问题。这就是我得出this.rpvCustomReport.RefreshReport();导致问题的结论。
  5. 问题

    1. 为什么会出现这个问题?
    2. 为了能够在将来调试此类问题,我应该执行哪些步骤?
    3. 解决方法

      为了解决这个问题,我必须在调用btnOk_Click之前取消该事件。

      private void rpvCustomReport_ReportRefresh(object sender, CancelEventArgs e)
      {
          e.Cancel = true; //Cancel the default event.
          this.btnOk_Click(null, null); //sender object and event variables are not used in this function, hence, it is set to null
      }
      

      我仍然不明白为什么我需要取消默认行为。这似乎不是一个好的解决方案。

5 个答案:

答案 0 :(得分:3)

我也在MSDN上发布了您对此问题的回复。

这与ReportViewer的内部异步呈现有关,当它导致它在当前操作的中间取消并重新开始时。我通过加载我的报告然后立即将显示模式设置为打印布局来遇到它。通过试验,可以通过使用以下代码向表单添加按钮然后重复单击它来减少为可重复的失败(如上所述,请注意,在调试器中运行时不会出现问题):

form.ReportViewer.SetDisplayMode(DisplayMode.PrintLayout);
form.ReportViewer.SetDisplayMode(DisplayMode.Normal);

在您的情况下,单击ReportViewer的刷新按钮会导致报告触发其内部刷新例程。该代码看起来像这样(使用JetBrains dotPeek提取,虽然微软现已开源,所以你可以在MS代码参考网站上找到):

private void OnRefresh(object sender, EventArgs e)
{
  try
  {
    CancelEventArgs e1 = new CancelEventArgs();
    if (this.ReportRefresh != null)
      this.ReportRefresh((object) this, e1);
    if (e1.Cancel)
      return;
    int targetPage = 1;
    PostRenderArgs postRenderArgs = (PostRenderArgs) null;
    if (sender == this.m_autoRefreshTimer)
    {
      targetPage = this.CurrentPage;
      postRenderArgs = new PostRenderArgs(true, false, this.winRSviewer.ReportPanelAutoScrollPosition);
    }
    this.RefreshReport(targetPage, postRenderArgs);
  }
  catch (Exception ex)
  {
    this.UpdateUIState(ex);
  }
}

请注意,引发了ReportRefresh事件,如果不取消此事件,ReportViewer将继续处理并重新呈现报告。事件处理程序中的代码也告诉ReportViewer刷新,这基本上就像我的代码那样设置了与ReportViewer相同的问题。

我原本打算将这一点与提交关于MS Connect的官方错误报告的想法进一步隔离,但我已经走了尽可能远的兔子洞了。我们从调用堆栈中知道的是一个线程正在切换执行上下文:

Description: The application requested process termination through System.Environment.FailFast(string message).
Message: An exception was not handled in an AsyncLocal<T> notification callback.
Stack:
   at System.Environment.FailFast(System.String, System.Exception)
   at System.Threading.ExecutionContext.OnAsyncLocalContextChanged(System.Threading.ExecutionContext, System.Threading.ExecutionContext)
   at System.Threading.ExecutionContext.SetExecutionContext(System.Threading.ExecutionContext, Boolean)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Threading.ThreadHelper.ThreadStart(System.Object)

当OnAsyncLocalContextChanged触发时,它会尝试处理变更通知的回调:

[SecurityCritical]
[HandleProcessCorruptedStateExceptions]
internal static void OnAsyncLocalContextChanged(ExecutionContext previous, ExecutionContext current)
{
    List<IAsyncLocal> previousLocalChangeNotifications = (previous == null) ? null : previous._localChangeNotifications;
    if (previousLocalChangeNotifications != null)
    {
        foreach (IAsyncLocal local in previousLocalChangeNotifications)
        {
            object previousValue = null;
            if (previous != null && previous._localValues != null)
                previous._localValues.TryGetValue(local, out previousValue);

            object currentValue = null;
            if (current != null && current._localValues != null)
                current._localValues.TryGetValue(local, out currentValue);

            if (previousValue != currentValue)
                local.OnValueChanged(previousValue, currentValue, true);
        }
    }

    List<IAsyncLocal> currentLocalChangeNotifications = (current == null) ? null : current._localChangeNotifications;
    if (currentLocalChangeNotifications != null && currentLocalChangeNotifications != previousLocalChangeNotifications)
    {
        try
        {
            foreach (IAsyncLocal local in currentLocalChangeNotifications)
            {
                // If the local has a value in the previous context, we already fired the event for that local
                // in the code above.
                object previousValue = null;
                if (previous == null ||
                    previous._localValues == null ||
                    !previous._localValues.TryGetValue(local, out previousValue))
                {
                    object currentValue = null;
                    if (current != null && current._localValues != null)
                        current._localValues.TryGetValue(local, out currentValue);

                    if (previousValue != currentValue)
                        local.OnValueChanged(previousValue, currentValue, true);
                }
            }
        }
        catch (Exception ex)
        {
            Environment.FailFast(
                Environment.GetResourceString("ExecutionContext_ExceptionInAsyncLocalNotification"),
                ex);
        }
    }
}

其中一个回调是抛出一个异常导致OnAsyncLocalContextChanged在其try / catch中调用Environment.FailFast,它将一个条目写入事件日志并立即终止该应用程序。

由于您可以在ReportViewer爆炸之前随机点击该按钮,因此该问题具有竞争条件的所有标志。现在,我们知道如何避免它。在我的情况下,我需要在刷新报告之前设置显示模式。对您而言,取消ReportRefresh事件可避免双重处理并解决您的问题,即使您不确切知道原因。也许那里的其他人都在关心进一步研究。

答案 1 :(得分:0)

可以替换代码

this.btnOk_Click(null, null);

this.btnOk_Click(sender, e);

如果没有解决问题,请告诉我

答案 2 :(得分:0)

你应该可以使用

btnOk.PerformClick();

Aslo,最好使用try - catch块并记录运行时发生的错误,但您始终无法调试应用程序的源代码。

答案 3 :(得分:0)

您可以调用不带参数的按钮单击功能。

btnOk.Click();或者btnOk.PerformClick();

或修改代码如下并尝试。

private void doSomeThing() {
try
    {
        ...
        //Code adding datasources to rpvCustomReport
        ...
        this.rpvCustomReport.RefreshReport(); //Causing the error.
        //NOTE: this.rpvCustomReport is instantiated in .Design.cs as
        //new Microsoft.Reporting.WinForms.ReportViewer();
        ...
    }
    catch (Exception ex)
    {
        HandleException(ex); //Custom function to handle exception
    }
}

private void btnOk_Click(object sender, EventArgs e)
{
    this.doSomeThing();
}

private void rpvCustomReport_ReportRefresh(object sender, CancelEventArgs e)
{
    this.doSomeThing();
}

答案 4 :(得分:0)

对于其他人来说有什么价值:ReportViewer控件似乎在处理背景渲染时存在错误。我决定侦听RenderingBeginRenderingComplete事件,并继续跟踪并发渲染尝试的次数,如下所示:

private int _isRendering = 0;

// Constructor code
reportViewer.RenderingBegin += ReportViewer_RenderingBegin;
reportViewer.RenderingComplete += ReportViewer_RenderingComplete;

private void ReportViewer_RenderingComplete(object sender, RenderingCompleteEventArgs e)
{
    _isRendering--;
}

private void ReportViewer_RenderingBegin(object sender, System.ComponentModel.CancelEventArgs e)
{
    _isRendering++;
}

我希望ReportViewer永远不会同时渲染多个。它似乎在总体上保护自己免受此类情况的侵害;即_isRendering始终为0或1。

我注意到对SetDisplayMode()的调用似乎并没有取消任何进行中的渲染,导致_isRendering变得大于1。例如,以下代码行将导致{{1} }等于2:

_isRendering

这与@Blake的研究“紧密地”联系在一起,后者表明很可能是某些种族状况引起了麻烦。

因此,尽管这实际上不是现成的答案,但我建议检查您的报告是否同时呈现。如果是这样,请特别确保检查(重复)对reportViewer.SetDisplayMode(DisplayMode.PrintLayout); reportViewer.SetDisplayMode(DisplayMode.Normal); 的呼叫。