C#:在 ReportViewer 中单击刷新按钮几次后应用程序崩溃 [英] C# : Application crashes after clicking Refresh button in ReportViewer a few times

查看:109
本文介绍了C#:在 ReportViewer 中单击刷新按钮几次后应用程序崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简介

这是 ReportViewer 在我的面向 .NET 框架 4.6.1 的 Winforms 应用程序中的外观的一部分.

This is a portion of how the ReportViewer looks like in my Winforms application which is targeting .NET framework 4.6.1.

OK 按钮调用 btnOk_Click 事件,而刷新按钮(圆圈中的双绿色箭头)调用 ReportViewer 事件,该事件本身调用带有空参数的 btnOK_Click 事件.下面的代码说明了它

The OK button calls the btnOk_Click event while the refresh button (Double-green arrows in a circle) calls the ReportViewer event which in itself calls btnOK_Click event with null parameters. The code below illustrates it

代码

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 中的刷新"按钮几次后,我的应用程序崩溃.

My application crashes after clicking Refresh button in ReportViewer a few times.

这是我在 Event Viewer > Windows Logs > Application

消息:AsyncLocal 通知回调中未处理异常. 我尝试在谷歌上搜索错误,但结果很短.

Message: An exception was not handled in an AsyncLocal notification callback. I tried googling for the error but came up short.

线索

  1. 此问题在调试模式 (Visual Studio 2015) 中不会发生
  2. 当我快速单击确定"按钮时不会出现此问题.
  3. 当我添加冗余/测试代码(例如,MessageBox.Show())after this.rpvCustomReport.RefreshReport(); 时不会发生此问题;btnOk_Click 事件中的 行.但是当我在该行之前添加它们时,问题就发生了.这就是我得出的结论是 this.rpvCustomReport.RefreshReport(); 导致了问题.
  1. This problem does not happen in debug mode (Visual Studio 2015)
  2. This problem does not happen when I rapidly click the OK button.
  3. This problem does not happen when I add redundant/test codes (e.g., MessageBox.Show()) after this.rpvCustomReport.RefreshReport(); line in btnOk_Click event. But when I add them before that line, the problem happens. This was how I concluded that the this.rpvCustomReport.RefreshReport(); was causing the problem.

问题

  1. 为什么会出现这个问题?
  2. 我应该执行哪些步骤才能在未来调试此类问题?

解决方法

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

In order to fix this, I have to cancel the event before calling 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
}

我还是不明白为什么我需要取消默认行为.这似乎不是一个好的解决办法.

I still don't understand why I need to cancel the default behavior. It doesn't seem like a good fix.

推荐答案

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

I posted this response to your question on MSDN also.

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

This has something to do with the ReportViewer's internal asynchronous rendering when you cause it to cancel in the middle of its current operation and start again. I ran into it by loading my report and then immediately setting the display mode to print layout. Experimenting, this can be reduced to a repeatable failure by adding a button to a form with the following code and then clicking it repeatedly (caveat is, as you noted, the issue does not occur when running in the debugger):

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

在您的情况下,单击 ReportViewer 的刷新按钮会导致报告触发其内部刷新例程.该代码如下所示(使用 JetBrains dotPeek 提取,尽管 Microsoft 现在已将其开源,因此您可以在 MS 代码参考站点上找到):

In your case, clicking the ReportViewer's refresh button causes the report to fire off its internal refresh routine. That code looks like this (extracted using JetBrains dotPeek, although Microsoft has open-sourced this now, so you can find on the MS code reference site):

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 进行旋转的相同问题.

Notice that the ReportRefresh event is raised and, if you do not cancel this event, the ReportViewer continues to process and re-render the report. The code you have in the event handler also tells the ReportViewer to refresh, which basically sets up the same issue of whipsawing the ReportViewer like my code did.

我最初打算通过在 MS Connect 上提交官方错误报告的想法进一步隔离这一点,但我已经尽可能地深入研究了这个兔子洞.我们从调用堆栈中知道的是一个线程正在切换执行上下文:

I had originally intended to isolate this further with the idea of filing an official bug report on MS Connect, but I've gone about as far down the rabbit hole as I care to go. What we do know from the call stack is that a thread is switching execution context:

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 触发时,它会尝试处理更改通知的回调:

When OnAsyncLocalContextChanged fires, it attempts to process callbacks for change notification:

[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,它将一个条目写入事件日志并立即终止应用程序.

One of those callbacks is tossing an exception causing OnAsyncLocalContextChanged to call Environment.FailFast in its try/catch, which writes an entry to the event log and immediately terminates the application.

由于您可以在 ReportViewer 崩溃之前随机多次单击按钮,因此该问题具有竞争条件的所有特征.现在,我们知道如何避免它.就我而言,我需要在刷新报告之前设置显示模式.对您而言,取消 ReportRefresh 事件可避免双重处理并解决您的问题,即使您不知道确切原因.也许外面的其他人关心进一步研究它.

Since you can click on the button a random number of times before the ReportViewer blows up, this issue has all the hallmarks of a race condition. For now, we know how to avoid it. In my case, I need to set the display mode before I refresh the report. For you, canceling the ReportRefresh event avoids double-processing and solves your problem, even though you did not know exactly why. Maybe someone else out there cares to look into it further.

这篇关于C#:在 ReportViewer 中单击刷新按钮几次后应用程序崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆