Blazor中两个独立任务同步:确保一个完成后再执行另一个
Blazor跨组件任务同步:解决标签切换与表单关闭时的RichEdit内容丢失问题
问题描述
在Blazor项目中遇到跨组件任务同步的棘手问题:两个由不同组件触发的任务调用顺序不可控,可能出现「需要等待的任务还没执行,必须完成的任务已经结束」的情况,导致功能异常。
核心场景:标签切换时的内容丢失
用DxFormLayoutTabPages实现标签页,它的ActiveTabIndexChanged事件和标签页内DxRichEdit的LostFocus事件存在冲突:
- 用户从含RichEdit的标签切换到其他标签时,两个事件都会触发
- 如果
ActiveTabIndexChanged先执行完成,RichEdit会被直接释放,导致LostFocus里调用的await RichEditDesc.SaveDocumentAsync()无法触发DocumentContentChanged,最终丢失编辑的内容
其他同类问题
用户在RichEdit标签页点击保存时,AppointmentFormClosing事件和LostFocus事件也有同样的同步冲突,需要一套统一的方案解决这类问题。
当前临时方案的弊端
之前用Task.Delay(1000)做延迟,存在两个大问题:
- 会导致UI卡顿,影响用户体验
- 延迟时长完全不可靠,不同性能的环境下可能失效
- 而且事件触发顺序可能是
ActiveTabIndexChanged先,也可能是LostFocus先,加上用户可多次切换标签,初始化信号量的方式根本行不通
相关Razor代码
<DxScheduler AppointmentFormClosing="AppointmentFormClosing"> <AppointmentFormLayout> <DxFormLayoutTabPages ActiveTabIndex="@ActiveIndex" ActiveTabIndexChanged="OnActiveTabIndexChanged"> <DxFormLayoutTabPage Caption="Details"> <DxRichEdit LostFocus="OnRichEditDescLostFocus" ModifiedChanged="OnDocumentDescModified" DocumentContentChanged="OnDocumentDescChanged"> </DxFormLayoutTabPage> </DxFormLayoutTabPages> </AppointmentFormLayout> </DxScheduler>
解决方案:用SemaphoreSlim实现可靠同步
针对这种场景,SemaphoreSlim是比Task.Delay靠谱得多的选择,它能确保关键任务执行完再跑后续操作,还支持多次触发的情况,具体实现步骤如下:
1. 定义同步锁
在组件的代码部分声明一个SemaphoreSlim对象,初始只允许一个任务执行,组件销毁时记得释放资源:
private readonly SemaphoreSlim _richEditSyncLock = new SemaphoreSlim(1, 1); public override void Dispose() { _richEditSyncLock.Dispose(); base.Dispose(); }
2. 改造RichEdit失焦事件
在OnRichEditDescLostFocus里,先获取锁再执行保存操作,确保保存过程中不会有其他操作销毁RichEdit:
private async Task OnRichEditDescLostFocus() { await _richEditSyncLock.WaitAsync(); try { if (RichEditDesc.IsModified) { await RichEditDesc.SaveDocumentAsync(); } } finally { // 不管成功失败都要释放锁,避免死锁 _richEditSyncLock.Release(); } }
3. 改造标签切换和表单关闭事件
在OnActiveTabIndexChanged和AppointmentFormClosing里,同样先等待锁释放,再执行切换或关闭逻辑:
private async Task OnActiveTabIndexChanged(int newIndex) { await _richEditSyncLock.WaitAsync(); try { ActiveIndex = newIndex; // 这里放你原来的标签切换逻辑 } finally { _richEditSyncLock.Release(); } } private async Task AppointmentFormClosing(SchedulerAppointmentFormClosingEventArgs args) { await _richEditSyncLock.WaitAsync(); try { // 表单关闭前的必要操作,比如检查并保存RichEdit内容 if (RichEditDesc.IsModified) { await RichEditDesc.SaveDocumentAsync(); } // 其他关闭逻辑 } finally { _richEditSyncLock.Release(); } }
方案优势
- 不管事件触发顺序如何,
SemaphoreSlim都会让后触发的任务等待先触发的完成,从根源上避免RichEdit被提前释放 - 没有固定延迟,不会导致UI卡顿,运行效率更高
- 支持用户多次切换标签、多次触发保存的场景,每次操作都会自动同步,不需要额外重置状态
内容的提问来源于stack exchange,提问作者David Thielen
相关产品推荐
相关产品推荐

