我正在开发一个遗留应用程序,该应用程序中有一个错误,并最终将其跟踪到一些以 event1 的形式执行的异步事件处理程序,以编程方式触发 event2。原始代码问题如下所示,为了阅读它,我简化了代码:
Public Async Sub MyBaseLoad() Handles MyBase.Load
//some async operations happen up here
Method1()
SomeMethodHere()
End Sub
Public Sub Method1()
' Set DataSource which triggers the SelectedIndexChanged event
myListBox.DataSource = myDataTable
End Sub
Public Async Sub Method2() Handles myListBox.SelectedIndexChanged
' Perform Basic Logic
Await LoadMyData()
Method3()
End Sub
Public Async Function LoadMyData() As Task(Of Boolean)
' Perform basic logic
Await CustomMethod()
End Function
Public Sub Method3()
' Code here not important
End Sub
发生的事情是,当
MyBaseLoad
通过 Method1
执行时,其处理触发了 SelectedIndexChanged
事件,该事件最终得到了 Await LoadMyData()
并且当它到达调用堆栈中的最后一个 Await 时 - CustomMethod
,它一直返回执行到 MyBaseLoad
,并且 SomeMethodHere
在 Method1
完成之前正在执行。因此,在 SomeMethodHere
之前调用 Method3
并产生了 bool 值错误的问题。我认为通过maybe使流程异步并等待,它会按照我想要的方式工作,所以我尝试了这样的事情:
Public Async Sub MyBaseLoad() Handles MyBase.Load
' Execute Method1 asynchronously
Await Method1()
SomeMethodHere()
End Sub
Public Async Function Method1() As Task
' Await MyListBoxPropAssigment asynchronously
Await MyListBoxPropAssigment()
End Function
Private Async Function MyListBoxPropAssigment() As Task
' Set DataSource which triggers the SelectedIndexChanged event
myListBox.DataSource = myDataTable
Await Task.Delay(0)
End Function
Public Async Sub Method2() Handles myListBox.SelectedIndexChanged
' Perform Basic Logic
Await LoadMyData()
Method3()
End Sub
Public Async Function LoadMyData() As Task(Of Boolean)
' Perform basic logic
Await CustomMethod()
End Function
Public Sub Method3()
' Code here not important
End Sub
这并没有改变任何东西,并且
SomeMethodHere()
仍在 Method3()
之前执行。现在,我可以通过使用 TaskCompletionSource(Of Boolean)
并更改 MyListBoxPropAssigment()
来等待 TaskCompletionSource
的任务来解决这个问题,其中结果在 Method2() 结束时设置为 true,但我不喜欢解决方案,我也不确定这是否是一个“好的”解决方案。有谁能够解释异步事件处理程序如何处理以及什么是好的解决方案?我认为触发 MyBase.Load
事件并不理想,防止其在 Load 上整个执行的 bool 是一种解决方案,但想了解核心问题。SelectedIndexChanged
的概念,以应对现有基础设施的限制。在您的情况下,您有两个同时运行的 async void
事件处理程序,因此它们的执行顺序是不可预测的。我不认为这个问题有任何漂亮的解决方案。您选择的解决方案(在表单中添加
async void
字段)对我来说听起来相当脆弱。它使本来就很复杂的问题变得更加复杂。我更愿意使用经典的解决方法,即使用 TaskCompletionSource<bool>
字段暂时挂起事件处理程序的执行。在我的项目中我通常这样做:
表格顶部:bool
然后我添加一个实用程序方法,该方法调用同步 (
private bool _eventsDisabled;
Action
) 操作,禁用事件处理程序,直到操作完成:
Func<Task>
所有事件处理程序的顶部:
private async Task WithEventsDisabled(Func<Task> action)
{
bool oldEventsDisabled = _eventsDisabled;
_eventsDisabled = true;
try
{
await action();
}
finally
{
_eventsDisabled = oldEventsDisabled;
}
}
使用
private async void myListBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (_eventsDisabled) return;
//...
}
实用方法来包装引发事件并造成混乱的调用:
WithEventsDisabled