[C#/UI] 使用 await 实现业务对 UI 的控制反转
背景:WPF/WinForm 桌面程序开发
问题#
在涉及到与用户交互的业务场景下,经常容易在界面的后台代码(也就是 xxx.xaml.cs)中编写业务逻辑,在这里调用业务层提供的方法。
如此一来,UI 的后台代码会变得臃肿,职责不清晰。而且由于与界面的耦合太深,后期修改需求会非常麻烦。
问题出在哪?
UI 应该只是提供基本的用户交互,不应该成为业务逻辑的控制中心,需要将业务代码放到独立的模块中,业务代码通过接口来调用 UI,以实现用户的交互。
控制反转 是指:应该有业务层代码调用 UI,而不是 UI 调用业务逻辑代码。
当然,最开始的调用一般是由 UI 发起的,这里强调的是:流程与逻辑的控制代码,应该在远离 UI 的业务层,UI 只负责用户交互。
改善措施#
容易想到的改善办法是:在 UI 中定义事件,业务层订阅事件,以获取用户操作的结果。
这样做是可以的,但实际写起代码来就会发现,使用事件订阅的方式,容易造成执行逻辑的割裂,代码的可读性会变得很差。
既然要等待用户操作的结果,除了事件之外,能否实现同步的等待呢?
比如,一个笨办法就是:写一个 while(true) 循环,不断检测用户是否完成了操作,如果完成了,就返回操作结果。
这样就不用使用事件调来调去了,可以同步等待用户完成操作。
使用 await#
当然,while(true) 的方案不会是真实的措施,使用 await 就可以实现这样的效果。
本质是,一个可等待的对象 awaiter 内部有一个通知机制,当你 await 一个对象之后,就会一直阻塞,等待通知。像不像是对事件的一种封装?哈哈。
这个通知机制就是 INotifyCompletion Interface (System.Runtime.CompilerServices) | Microsoft Docs
代码就是:await 用户操作();
,如果用户操作没有完成,则这里就阻塞。
如此一来,业务逻辑写起来就会顺畅很多。
具体实现原理与方法可以看:
在 WPF/UWP 中实现一个可以用 await 异步等待 UI 交互操作的 Awaiter - walterlv
Demo 分析#
Demo:Jasongrass/DemoPark - 码云 - 开源中国
使用事件实现的流程控制代码:
public void DoFlow()
{
_isUnderFlowing = true;
Step1();
}
private void OnUserInputFinished(object sender, string inputContent)
{
// 状态判断,如有没有执行 Step1 等。如果状态判断OK,则执行 Step2。
if (!_isUnderFlowing)
{
return;
}
var precessResult = Step2(inputContent);
StepEnd();
// 外部如何拿到 precessResult?
}
会发现,整个流程被分成了两部分,而且没法很好地返回最终处理结果(因为代码在事件的响应里面)。
使用 awaiter 实现的流程控制代码:
public async Task<string> DoFlowAsync()
{
Step1();
var inputContent = await UserInputViewHandler.GetUserInputAsync();
var precessResult = Step2(inputContent);
StepEnd();
return precessResult;
}
可以在一个函数里面,处理所有的逻辑。
更重要的是,这里还是只有一个 UI 交互的场景,在需要更多的 UI 交互时,如果使用事件的实现方式,代码理解起来将是一个灾难。
核心代码#
UI 部分要支持这种调用当时,需要的核心代码其实很少。
使用 walterlv 封装的这个 DispatcherAsyncOperation
类,实现对用户操作的 awaiter 等待,会很轻松。
基础原理文章:
在 WPF/UWP 中实现一个可以用 await 异步等待 UI 交互操作的 Awaiter - walterlv
Demo源代码:
Jasongrass/DemoPark - 码云 - 开源中国
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2019-02-14 Git 无法拉取,Unlink of file '.git/objects/pack/pack-***.pack' failed. Should I try again? (y/n)