在开发网页应用时,经常会遇到页面加载缓慢的问题。例如,一个网页开始显示,但下拉菜单显示不完整,并且页面会卡顿6-7秒,直到数据加载完毕,页面才正确显示。这种情况通常是因为开发者将耗时的代码(如加载SQL Server数据)放在了Page_Load函数中,导致浏览器在数据加载完成之前几乎无法响应。
本文将指导如何通过将耗时的代码移至异步线程,并在数据准备就绪后更新网页,来解决这个问题。
免责声明:使用现代技术如Angular加载和显示数据无疑更加高效和易于维护,本文旨在提供一个简单的步骤指南,以快速改进旧版页面,而无需重写所有代码。
开发者将从SQL Server加载数据的代码放在了Page_Load函数中。通过创建一个10秒的延迟,然后生成一个包含样本数据的DataTable,并将其设置为DataGrid的数据源,来模拟这个问题。
protected void Page_Load(object sender, EventArgs e) {
if (IsPostBack) return;
// 等待10秒...
System.Threading.Thread.Sleep(10000);
// ...然后创建一个包含样本数据的DataTable...
System.Data.DataTable dt = new System.Data.DataTable("Drivers");
dt.Columns.Add("UserID", Type.GetType("System.Int64"));
dt.Columns.Add("Surname", Type.GetType("System.String"));
dt.Columns.Add("Forename", Type.GetType("System.String"));
dt.Columns.Add("Sex", Type.GetType("System.String"));
dt.Columns.Add("Date of Birth", Type.GetType("System.DateTime"));
dt.Rows.Add(new object[] {1, "James", "Spencer", "M", new DateTime(1962, 3, 19)});
dt.Rows.Add(new object[] {2, "Edward", "Jones", "M", new DateTime(1939, 7, 12)});
dt.Rows.Add(new object[] {3, "Janet", "Spender", "F", new DateTime(1996, 1, 7)});
dt.Rows.Add(new object[] {4, "Maria", "Percy", "F", null});
dt.Rows.Add(new object[] {5, "Malcolm", "Marvelous", "M", new DateTime(1973, 5, 7)});
// ...并将其绑定到ASP.Net GridView控件。
this.grid.DataSource = dt;
this.grid.DataBind();
}
这段代码的结果是网页加载非常缓慢。通常浏览器在显示页面的过程中会卡顿几秒钟,然后才能显示完整的网页。
首先,需要在.aspx文件中找到包含需要更新的控件的部分。在示例中,只有DataGrid需要在数据加载完成后更新。
<asp:DataGrid ID="grid" runat="server">
</asp:DataGrid>
需要将这个控件(或一组控件)包裹在UpdatePanel和ContentTemplate中,并在页面上添加一个Timer控件。
<asp:UpdatePanel ID="panel" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Timer ID="MyTimer" OnTick="timer_tick" Interval="1000" runat="server" />
<asp:DataGrid ID="grid" runat="server">
</asp:DataGrid>
</ContentTemplate>
</asp:UpdatePanel>
目标是尽快将网页显示在屏幕上,一旦数据加载完成,就可以回去更新网页的这一部分。
接下来,需要将数据加载代码从Page_Load函数中分离出来,放入自己的函数中,并使其填充一个变量(在本例中为DataTable),该变量存储在Session变量中。
System.Data.DataTable dt
{
get { return (System.Data.DataTable)Session["table1"]; }
set { Session["table1"] = value; }
}
bool bReadyToDisplayData
{
get { return (bool)Session["bReadyToDisplayData"]; }
set { Session["bReadyToDisplayData"] = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack) return;
bReadyToDisplayData = false;
LoadDataFromWebService();
}
private void LoadDataFromWebService()
{
// (这将是从某个web服务加载数据的耗时函数)
// 等待10秒...
System.Threading.Thread.Sleep(10000);
// ...然后创建一个包含样本数据的DataTable...
dt = new System.Data.DataTable("Drivers");
// ...
// 一旦所有数据加载完成,设置这个布尔变量,将触发DataGrid控件显示新加载的数据。
bReadyToDisplayData = true;
}
这看起来更好,但仍然同步调用数据加载函数,所以接下来,让改变Page_Load以异步调用它。
System.Threading.Thread thread = new System.Threading.Thread(LoadDataFromWebService);
thread.Start();
最后,需要添加一个Timer "tick"处理程序来将所有内容联系起来。
protected void timer_tick(object sender, EventArgs e)
{
// 每秒,网页将调用这个函数。
if (bReadyToDisplayData == false) {
return; // 后台线程仍在运行。
}
// 数据已经加载完成!
// 使用JSON数据填充Grid
this.grid.DataSource = dt;
this.grid.DataBind();
// 现在可以更新UpdatePanel,并停止计时器。网页现在完成了!
this.panel.Update();
MyTimer.Enabled = false;
}
就是这样!现在,当打开这个aspx网页时,它会非常快地显示出来,然后去加载它的数据,当它完成后,将设置DataGrid的DataSource指向这些数据,并让它显示出来。
如果发现这段代码对不起作用,请检查.aspx页面是否能够保存Session变量的值。要做到这一点,在Page_Load函数中设置bReadyToDisplayData变量后放置一个断点。
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack) return;
bReadyToDisplayData = false;
// 在下一行放置一个断点...
System.Threading.Thread thread = new System.Threading.Thread(LoadSomeData);
// 如果然后检查Session变量,它的"Count"值应该至少为1(因为刚刚添加了一个Session变量来存储bReadyToDisplayData变量)。
// 如果这个"Count"值为0,那么网页没有存储Session变量,这段代码将无法正常工作。
需要检查的两件事:
protected void Session_Load(object sender, EventArgs e)
{
Session["info"] = 1;
}
这个Session变量问题似乎主要在Internet Explorer 11上被注意到。