背景: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 - 码云 - 开源中国