我有一个基于 .Net Framework 4.8 构建的网站。我有一个按钮,当单击它时,它将按顺序同步对服务器进行远程调用。可能有数千个操作,每个操作可能需要很多秒才能响应。
因此,我希望页面上有一个标签指示当前进度,例如“执行的 1/1000 操作”和“当前操作:启动”。我怎样才能实现这个目标?
这是模拟该场景的示例代码:
<asp:Button ID="StartButton_Click" OnClick="StartButton_Click" Text="Start" runat="server"/>
<asp:UpdatePanel ID="ProgressUpdatePanel" runat="server">
<ContentTemplate>
<asp:Label ID="ProgressLabel" runat="server"></asp:Label>
</ContentTemplate>
<Triggers>
</Triggers>
</asp:UpdatePanel>
以及模拟远程调用的处理程序:
protected void StartButton_Click(object sender, EventArgs e)
{
List<string> actions = (List<string>)Enumerable.Repeat("0", 100);
processItem(actions, 0);
}
private void processItem(List<string> items, int index)
{
if(index >= items.Count)
{
return;
}
else
{
System.Threading.Thread.Sleep(3000);
/* Can't update ProgressLabel.Text here,
because I can't update anything before the page is returned to the client*/
processItem(items, index + 1);
}
}
我得到的最接近的是使用 TimerControl 执行 asyncPostBacks 来读取进度并更新标签,但这感觉像是一种解决方法,我想知道是否有更好的方法。
<asp:Button ID="StartButton_Click" OnClick="StartButton_Click" Text="Start" runat="server"/>
<asp:Timer ID="Timer1" OnTick="Timer1_Tick" runat="server" Interval="1000" Enabled="false" />
<asp:UpdatePanel ID="ProgressUpdatePanel" runat="server">
<ContentTemplate>
<asp:Label ID="ProgressLabel" runat="server"></asp:Label>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Timer1" />
</Triggers>
</asp:UpdatePanel>
static currentCount = 0; //updated by processItem
protected void Timer1_Tick(object sender, EventArgs e)
{
ProgressLabel.Text = $"{currentCount}/{totalCount} actions performed";
}
我也尝试过在
ViewState
中强制回发并保存状态,但这需要在客户端自动触发回发,感觉真的很不对。
首先,20 个,或者 100 个?那么好吧。但是,您的帖子建议在 1000+ 范围内。这是一个非常不同的挑战和计算机问题。
请记住,谷歌、微软等公司在面试中会提出上述类型的问题(诸如多大、多远、多长的问题)。出现此类问题的原因是,网页上的某些进度系统(例如 20 个甚至 50 个项目)之间存在巨大差异。但是,1000+ 是一个全新的不同类型的问题和挑战。
网站不太可能需要运行和控制 1000 多个要做的事情。网站可能会“监视”此类过程的进展情况,但不负责运行此类代码......
这不再是网站类型的应用程序。
我们必须区分“实时”更新和监控,而不是说调用一些 Web 服务器代码 + 运行 1000 个不同的进程。
这很像我们每 5 秒获取并显示一些机器设备的温度读数,甚至是用于显示的外部温度计值的差异。
当我们开始谈论 1000+ 个进程时,我们就无法知道、不知道、也不会知道每个进程运行所需的时间。而且网站不够可靠,无法负责调用这么多例程。 然而,针对此类处理的某种轮询是有意义的。
那么,比如说 40 次或 75 次操作?那么当然,我们可以基于网络进行此操作。
但是,在我们发布一些适用于简单用例的简单代码之前?
如果您正在寻找实时更新?
那么您最好采用 SignalR,因为它是专为此类任务而设计的。
SignalR 证明服务器与浏览器的实时 Web 套接字连接。因此,对于“与朋友聊天”类型的应用程序来说,两者都可以在网络浏览器中输入内容,并且都可以实时看到彼此正在输入的文本。
因此,SignalR 技术也适用于工厂车间的监控设备,甚至是外部温度的简单显示。因此,对于这种类型的实时更新(对于超出正常情况的疯狂情况,您有 1000 多个进程需要监控)?
您可以从这里开始使用 SignalR (它是为这些实时更新和过程监控类型的问题而设计的)。 SignalR 将允许您的服务器端代码甚至在循环中将状态更新到浏览器中)。
因此,如果您需要跟踪 1000 多个处理事件,那么 SignalR 就是您的最佳选择:
链接在这里:
https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/
但是,对于我们这些凡人来说,还有一个简单的处理和更新循环吗?
说出这个标记:
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Button ID="cmdProcess" runat="server" Text="to hide button" ClientIDMode="Static"
OnClick="cmdProcess_Click" style="display:none" />
<asp:Button ID="Button1" runat="server" Text="Start the Reactor"
ClientIDMode="Static" CssClass="btn"
OnClientClick="startprocessor('Processing',1,18);return false"
/>
<br />
<asp:HiddenField ID="pStep" runat="server" ClientIDMode="Static" />
<asp:HiddenField ID="pSteps" runat="server" ClientIDMode="Static" />
<div id ="MessageArea" style="display:none">
<style>
.ui-progressbar-value {background: lightskyblue;}
</style>
<div id="pbar"
style="width:400px">
</div>
<asp:Label ID="lblmsg" runat="server" Text="" ClientIDMode="Static"></asp:Label>
<br />
</div>
</ContentTemplate>
</asp:UpdatePanel>
<script>
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_endRequest(EndRequestH);
function EndRequestH(sender, args) {
// code here
lbl = $('#lblmsg')
if (lbl.text() !== "") {
// more processing required
$('#MessageArea').show()
iStep = $('#pStep').val()
iSteps = $('#pSteps').val()
lbl.text("(" + iStep + "/" + iSteps + ") " + lbl.text())
$('#pbar').progressbar({ value: iStep / iSteps * 100 });
$('#cmdProcess').click()
}
}
function startprocessor(sMsg, iStep, iSteps) {
var mymsg = "(1/" + iSteps + ") " + sMsg
$('#lblmsg').text(sMsg)
$('#pStep').val(iStep)
$('#pSteps').val(iSteps)
EndRequestH()
}
</script>
因此,我们有一个更新面板,设置处理步骤,然后单击按钮。后面的代码运行,完成后,客户端代码检查下一步,如果是,则再次单击按钮。
背后代码:
protected void Page_Load(object sender, EventArgs e)
{
}
protected void cmdProcess_Click(object sender, EventArgs e)
{
int Step = Convert.ToInt32(pStep.Value);
int Steps = Convert.ToInt32(pSteps.Value);
switch (Step)
{
case 1:
// code here for step one
Thread.Sleep(2000);
// if more steps, then setup next message
pStep.Value = "2";
lblmsg.Text = "Sending emails...";
return;
case 2:
// code here for step 2
Thread.Sleep(3000);
pStep.Value = "3";
lblmsg.Text = "Building Projects for customers...";
return;
case 3:
// code here for step 3 - Building project for custoemrs
Thread.Sleep(2000);
pStep.Value = "4";
lblmsg.Text = "Taking pet dog for a walk..";
return;
case int n when (n > 3) && (n < Steps):
Thread.Sleep(1000);
pStep.Value = (Step + 1).ToString();
lblmsg.Text = "Working, please wait";
return;
case int n when (n == Steps):
// process code here for last step
Thread.Sleep(1000);
// we done last step, so stop
// last step - clear out lbl msg to STOP processing
lblmsg.Text = ""; // blank message - stop processor
return;
}
}
结果如下所示:
因此,上述方法适用于大约 20 或 75 个流程步骤。如果您需要更多步骤,那么您需要采用不同的技术,例如 SignalR。