当前位置: 首页 > news >正文

针对WPF的功耗优化(节能编程)

一、UI渲染优化

1. 减少不必要的视觉元素

<!-- 避免过度使用复杂效果 -->
<Border Background="LightGray" CornerRadius="5" Margin="5" Padding="10"><!-- 使用简单样式代替复杂模板 -->
</Border><!-- 而不是 -->
<Border Background="LightGray" CornerRadius="5" Margin="5" Padding="10"><Border.Effect><DropShadowEffect BlurRadius="10" ShadowDepth="3"/></Border.Effect>
</Border>

2. 优化动画使用

// 仅在必要时运行动画
private void StartAnimationIfNeeded()
{if (SystemParameters.PowerLineStatus == PowerLineStatus.Online || BatteryStatus.HasPowerSource){// 只有在外接电源或电量充足时运行动画
        BeginAnimation();}
}

二、 线程和定时器优化

1. 线程

优先使用线程池,避免随意创建新线程    

  • 原理:创建/销毁线程开销巨大(触发CPU调度,很耗电)。线程池维护一组可重用的工作线程,避免了这种开销。
  • 做法:使用 Task.Run(...) 或 Task.Factory.StartNew(...) 默认就使用了线程池。
// 糟糕!直接创建新线程,成本高且难以管理
for (int i = 0; i < 100; i++)
{// 非常耗电new Thread(() => DoWork(i)).Start(); 
}
------------------------------------------------------------------------
// 优秀!使用线程池(通过Task API)
for (int i = 0; i < 100; i++)
{// 高效、节能Task.Run(() => DoWork(i)); 
}
// 或者使用Parallel.For,它内部也使用线程池并进行优化
Parallel.For(0, 100, i => DoWork(i));

使用高效的同步机制,避免“忙等待”(Busy Waiting)  https://www.cnblogs.com/LXLR/p/17659144.html

  • 原理:忙等待(如 while(flag) {})会使CPU核心在该循环中持续以100%的占用率空转,极度耗电且毫无意义。
  • 做法:使用基于内核事件的同步原语,如 AutoResetEvent, ManualResetEvent, Semaphore, Monitor (C# lock), Mutex。这些原语会在等待时让线程阻塞(Block),线程会被移出调度队列,CPU核心可以立即去执行其他任务或进入空闲状态。
// 糟糕!忙等待,CPU核心疯狂空转
private volatile bool _isDataReady = false;
public void BusyWaitMethod()
{    while (!_isDataReady) { } //极度耗电!
    ProcessData();
}
-------------------------------------------------------------------------------------------
// 优秀!使用事件等待,线程阻塞,CPU可休息
private readonly AutoResetEvent _dataReadyEvent = new AutoResetEvent(false);
public void EfficientWaitMethod()
{_dataReadyEvent.WaitOne(); // 线程在此挂起,不消耗CPU时间
    ProcessData();
}
// 另一个线程在数据准备好后调用 _dataReadyEvent.Set() 来唤醒它

2. 定时器  https://www.cnblogs.com/LXLR/p/17696125.html

特性
DispatcherTimer
System.Timers.Timer
System.Threading.Timer
线程模型
UI 线程
线程池工作线程 (可通过 SynchronizingObject 封送到 UI 线程)
线程池工作线程
是否便于更新 UI
是 (天然在 UI 线程执行)
需通过 SynchronizingObject 或 Dispatcher.Invoke
需通过 Dispatcher.Invoke
节能程度
相对较低 (会阻止 UI 线程进入空闲状态)
较高
通常最高 (对 UI 线程干扰最小)
适用场景
需要频繁更新 UI 元素的低频操作
一般后台任务,可能需要与 UI 交互
纯后台任务、资源清理、低频心跳
精度
依赖 UI 消息循环,精度较低
较高
较高 (但受线程池调度影响)
是否需要处理跨线程访问
是 (当需要更新 UI 时)
是 (当需要更新 UI 时)
为何 System.Threading.Timer 通常更节能
  • 基于线程池(ThreadPool):System.Threading.Timer 和 System.Timers.Timer 的回调方法都在.NET线程池的工作线程上执行,而不是专用的线程。线程池能有效控制和重用线程,避免了频繁创建和销毁线程的开销。
  • 最小化UI线程干扰:因为它们不直接占用UI线程,UI线程可以更自然地进入空闲或低功耗状态。而 DispatcherTimer 的 Tick 事件总是在UI线程触发,这意味着即使任务简单,也会唤醒并占用UI线程,可能会阻止系统进入更深层次的空闲状态。
选择定时器与节能建议:
  • 需要更新UI元素:优先选用 DispatcherTimer。
  • 执行后台任务、无需更新UI:优先选用 System.Threading.Timer。
  • 需要与UI交互的后台任务:可考虑 System.Timers.Timer。
无论选择哪种定时器,这些做法都有助于降低能耗:
  • 使用尽可能长的间隔:将 Interval 设置为业务逻辑允许的最大值。频繁触发(如100ms)相比低频触发(如2000ms)能耗差异巨大。
  • 及时停止和清理:在窗口关闭、页面卸载或不再需要定时器时,务必调用:
    • DispatcherTimer: .Stop() 方法
    • System.Timers.Timer: .Stop() 和 .Dispose() 方法 (或设置 Enabled = false)
    • System.Threading.Timer: .Change(Timeout.Infinite, Timeout.Infinite) 和 .Dispose() 方法
  • 避免在回调中执行繁重操作:尤其在 DispatcherTimer 中,长时间的计算会阻塞UI线程,消耗更多资源,造成界面卡顿。应考虑将耗时操作异步化或移至工作线程。
  • 考虑使用单个定时器处理多个任务:与其为每个任务创建一个定时器,不如使用一个定时器,在其回调中遍历处理所有需要定期执行的任务集合。
  • 对于高频或精确计时需求:评估是否有更高效的替代方案,如 Rx.NET 的 Observable.Timer 或基于帧渲染的 CompositionTarget.Rendering 事件(适用于UI动画)。

三、 数据结构优化

核心原则是:没有绝对最优的数据结构,只有针对特定场景最合适的数据结构。
主要需求
避免
推荐
快速查找(是否存在)
List<T>
O(n)
O(1)
键值对快速查找
List<KeyValuePair<K,V>>
O(n)
Dictionary<K, V>
O(1)
需要有序性 & 快速查找
N.A
N.A
SortedDictionary<K, V>、SortedList<K, V>
O(log n)
频繁在首/尾增删元素
List<T>
O(n)
LinkedList<T>
O(1)
先进先出(FIFO)队列
List<T>
在首部删除非常低效
Queue<T>
O(1)
后进先出(LIFO)栈
List<T>
在尾部操作高效,但语义不专一
Stack<T>
O(1)
需要快速获取最大/最小元素
每次遍历查找
O(n)
PriorityQueue<T> (.NET 6+)
O(log n)

经典场景与代码示例

1. 场景:检查用户名是否已被注册

  • 低效做法: 使用 List<string>
List<string> registeredUsers = GetUsersFromDB(); // 假设有10万个用户// 每次检查都要遍历10万条数据 -> O(n)
bool isTaken = registeredUsers.Contains("newUsername");
  • 高效做法: 使用 HashSet<string>
// 从数据库获取后,直接放入HashSet。构建过程是O(n),但只做一次。
HashSet<string> registeredUsersSet = new HashSet<string>(GetUsersFromDB());// 后续每次检查都是近乎即时的 -> 平均O(1)
bool isTaken = registeredUsersSet.Contains("newUsername");   

 

2. 场景:通过产品ID快速获取产品信息

  • 低效做法: 使用 List<Product>
List<Product> products = GetProducts();
// 需要遍历整个列表查找 -> O(n)
Product desiredProduct = products.FirstOrDefault(p => p.Id == targetId);
  • 高效做法: 使用 Dictionary<int, Product>
// 以ID为键,产品对象为值构建字典
Dictionary<int, Product> productDictionary = GetProducts().ToDictionary(p => p.Id);// 直接通过键获取,无需遍历 -> O(1)
if (productDictionary.TryGetValue(targetId, out Product desiredProduct))
{// 找到了
}

 

3. 场景:处理需要按优先级执行的任务

  • 高效做法: 使用 PriorityQueue<T>
// 定义一个任务类,实现IComparable接口或提供IComparer来比较优先级
PriorityQueue<WorkTask, int> taskQueue = new PriorityQueue<WorkTask, int>();// 入列任务,优先级数字越小通常表示优先级越高(可根据需求调整)
taskQueue.Enqueue(new Task("Low importance"), priority: 3);
taskQueue.Enqueue(new Task("Critical!"), priority: 1);
taskQueue.Enqueue(new Task("Medium"), priority: 2);// 按优先级顺序处理任务(Critical -> Medium -> Low)
while (taskQueue.TryDequeue(out WorkTask task, out int priority))
{ProcessTask(task); // 每次Dequeue操作是O(log n)
}

四、电源状态感知

public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();SystemEvents.PowerModeChanged += OnPowerModeChanged;}private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e){switch (e.Mode){case PowerModes.StatusChange:AdjustForBatteryMode();break;case PowerModes.Resume:ResumeOperations();break;case PowerModes.Suspend:ReduceActivity();break;}}private void AdjustForBatteryMode(){// 切换到电池模式时的优化if (SystemInformation.PowerStatus.PowerLineStatus == PowerLineStatus.Offline){ReduceAnimations();IncreaseUpdateIntervals();DisableNonEssentialFeatures();}}
}

五、后台任务优化

1. 合并与批量处理任务

  • 原理:类似于网络请求的合并。CPU从休眠到激活有一次“唤醒成本”,处理100个任务唤醒1次,远比处理100次任务唤醒100次要省电得多。
  • 做法:使用生产者-消费者模式,将零散产生的任务放入一个队列或缓冲区,然后由一个或少数几个后台线程以固定间隔批量处理。
  • 示例:日志记录、数据采集、文件写入等场景非常适合此策略。
// 一个简单的生产者-消费者示例
private readonly BlockingCollection<LogMessage> _logQueue = new BlockingCollection<LogMessage>();// 启动一个消费者线程
public void StartLogger()
{Task.Run(async () =>{var batch = new List<LogMessage>();while (true){// 等待第一条日志var message = _logQueue.Take();batch.Add(message);// 尝试在短时间内收集一批日志,而不是来一条写一条while (_logQueue.TryTake(out message, timeout: 50)) // 等待50ms看还有没有新日志
            {batch.Add(message);}await WriteLogBatchToFile(batch); // 批量写入文件
            batch.Clear();}});
}// 生产者(应用线程)
public void Log(string text)
{_logQueue.Add(new LogMessage(text));
}

 

2. 优化算法和数据结构,减少计算量

  • 原理:这是最根本的省电方式。CPU执行指令越少,耗电自然越少。
  • 做法:
    • 选择时间复杂度更低的算法(O(n) vs O(n²))。
    • 使用更高效的数据结构(HashSet<T> 用于查找 vs List<T>)。
    • 避免在循环中进行不必要的计算、资源分配(如创建对象)和密集的I/O操作。
    • 使用缓存(Cache)来存储昂贵计算的结果。
http://www.wxhsa.cn/company.asp?id=2044

相关文章:

  • Docker 清理完整指南:释放磁盘空间的最佳实践 - 详解
  • 微算法科技(NASDAQ: MLGO)开发Rollup技术,探索区块链扩展性解决方案
  • 征稿倒计时3天/武汉科技大学主办/医学人工智能/现可享优惠
  • 生成更智能,调试更轻松,SLS SQL Copilot 焕新登场!
  • NOI linux使用教程
  • springboot 文件处理框架
  • Docker:龙晰系统(Anolis)更新yum源下载docker
  • 针对单输入单输出、多输入多输出及三阶系统带约束的模型预测控制的实现
  • vue3中父子组件数据同步的默认方式update:xxx
  • 解决 C# 当另一个read操作挂起时不能调用read方法的问题
  • AI辅助编程_工具和方式
  • [完结10章]Java大模型工程能力必修课,LangChain4j 入门到实践
  • k8s源码分析——kubectl命令行交互
  • 将 seata 2.5 发布到私服
  • 一些感悟
  • 五款免费低代码平台深度横评:斑斑、简道云、宜搭、氚云、织信如何选?
  • ubuntu历史版本下载
  • 读书笔记:数据库索引的智能优化:反向键与降序索引
  • 代码随想录算法训练营第十天| 232.用栈实现队列、 225. 用队列实现栈、20. 有效的括号 、1047. 删除字符串中的所有相邻重复项
  • 零成本搭建企业系统:五款免费低代码平台推荐
  • 故障处理:access$表在数据库丢失的恢复
  • 从需求出发:教你判断选斑斑还是织信
  • PLC结构化文本设计模式——建造者模式(Builder Pattern)
  • C++ - STL - 迭代器
  • MATLAB的智能扫地机器人工作过程仿真
  • linux redis 8.2.1软件开机启动redis.service与etc下的rc.local配置2种方式
  • 在GA中添加Tag-GetDynamicSpecSourceTags().AddTag(NewTag)
  • python如何在函数中使用全局变量?
  • 296、贾生
  • ubuntu 24.04部署mysql8.0.41(glibc2.17)