Introduction
This is a portion of how the ReportViewer looks like in my Winforms application which is targeting .NET framework 4.6.1.
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
Code
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
}
Problem
My application crashes after clicking Refresh button in ReportViewer a few times.
This is what I found in the Event Viewer > Windows Logs > Application
Message: An exception was not handled in an AsyncLocal notification callback. I tried googling for the error but came up short.
Clues
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.Questions
Workaround
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.
I posted this response to your question on MSDN also.
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);
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);
}
}
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.
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)
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);
}
}
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With