我有一个自动化程序用于控制其他应用程序(例如浏览器或桌面应用程序)。 我创建了一个带有“while”的函数来确定应用程序的当前页面或状态,从而决定要执行的下一个操作。
void OuterMostFunc()
{
while(!exit)
{
//InitSomethings...
InnerFunc();
//HandleSomethings...
Thread.Sleep(milliseconds);
}
}
void InnerFunc()
{
if(A_Page_Cond)
A_Func();
else if(B_Page_Cond)
B_Func();
...
}
但同时,这些应用程序可能会被手动操作或遇到意外事件而改变页面或状态。 因此,我必须检查很多关键点的状态变化,并突破返回最外层函数重新评估情况。 一个相对优雅的解决方案是利用 Try-Catch 以及自定义 BreakException,允许我从代码中的任何位置返回到最外层函数。 此外,我在最外层重置了所有关键变量,因此我不需要担心中断引起的问题。
void OuterMostFunc()
{
while(!exit)
{
//InitSomethings...
try
{
InnerFunc();
}
catch(BreakExcetion ex)
{}
//HandleSomethings...
Thread.Sleep(milliseconds);
}
}
void Any_Deep_Level_Func()
{
//DoSomethings.
if(CheckPageIsChanged())
throw new BreakException();
//DoSomethings.
}
长期以来,这种方法一直有效且可靠。 然而,由于程序的应用上下文要求最高的速度,Try-Catch带来的性能开销变得难以忽视。 例如,Try-Catch需要数十毫秒的执行时间,对CPU造成了较大的负担。”
如果每个函数都返回bool(或out bool),然后我们在调用每个函数后检查是否返回或执行下一步,确实可以让我们快速返回到最外层函数。 然而,这种方法似乎对代码的可读性造成了灾难,特别是考虑到程序中调用的函数的实际复杂性和多层性质。
补充说明:
由于它是同步运行的,因此持续运行的 while 循环本身就会对性能产生不利影响,尤其是在使用
Thread.Sleep
调用时。 不要这样做。切换到异步实现。
以下是在最近的 .NET 版本中使用 BackgroundService 实现长时间运行的应用程序(例如 Windows 服务或 Web 服务)的常见方式:
public sealed class AppService : BackgroundService
{
// change the update interval as you like, or read from config
private static readonly _interval = TimeSpan.FromMilliseconds(20);
private readonly ILogger<Worker> _logger;
public AppService(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// use asynchronous check if possible
// (synchronous might also be fine here)
if (await CheckChangedAsync(stoppingToken))
{
// do something
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
// was canceled
break;
}
catch (Exception exception)
{
_logger.LogError(exception, "Unexpected exception occured");
// throw or exit gracefully
throw;
}
// pause to free resources before the next update
await Task.Delay(_interval, stoppingToken);
}
}
}
注:
Thread.Sleep
仍然会耗尽线程,所以异步方法更好_interval
,而只依赖于 CheckChangedAsync