我的一般性问题是:如何编写异步代码,使其仍然清晰易懂,就像同步解决方案一样?
我的经验是,如果你需要将一些同步代码变成异步代码,使用类似BackgroundWorker的东西,你将不再有一系列易于遵循的程序语句,表达你的整体意图和活动顺序,相反,你最终会得到一堆“完成”事件处理程序,每个事件处理程序都会启动下一个BackgroundWorker,从而产生非常难以理解的代码。
我知道这不是很清楚;以下更具体:
假设我的WinForms应用程序中的一个函数需要启动一些amazon EC2实例,等待它们变成运行状态,然后等待它们全部接受SSH连接。一个同步解决方案的伪代码可能看起来像这样:
很好。发生的事情很清晰,程序操作的顺序也很明确。没有白噪声干扰你理解代码和流程。我真的希望最终的代码看起来是这样的。
但实际上,我不能使用同步解决方案,因为每个函数都可能运行很长时间,并且每个函数都需要做诸如:更新UI、监视超时是否超过以及定期重试操作直到成功或超时等操作。简而言之,每个操作都需要在后台进行,以便前台UI线程可以继续进行。
但是,如果我使用BackgroundWorker之类的解决方案,似乎就无法像上面那样拥有易于跟踪的程序逻辑了。相反,我可能会从我的UI线程启动一个背景工作线程来执行第一个函数,然后我的UI线程回到UI,而工作线程在运行。当它完成时,它的“完成”事件处理程序可能会启动下一个Background Worker。当它完成时,它的“完成”事件处理程序可能会启动最后一个Background Worker,以此类推。这意味着您必须“跟随”Done事件处理程序才能了解整个程序流程。
一定有更好的方法,使得a)让我的UI线程响应,b)让我的异步操作能够更新UI,最重要的是c)能够将我的程序表达为一系列连续的步骤(如上所示),以便其他人可以理解生成的代码。
任何建议都会非常感激! 迈克尔
我的经验是,如果你需要将一些同步代码变成异步代码,使用类似BackgroundWorker的东西,你将不再有一系列易于遵循的程序语句,表达你的整体意图和活动顺序,相反,你最终会得到一堆“完成”事件处理程序,每个事件处理程序都会启动下一个BackgroundWorker,从而产生非常难以理解的代码。
我知道这不是很清楚;以下更具体:
假设我的WinForms应用程序中的一个函数需要启动一些amazon EC2实例,等待它们变成运行状态,然后等待它们全部接受SSH连接。一个同步解决方案的伪代码可能看起来像这样:
instances StartNewInstances() {
instances = StartInstances()
WaitForInstancesToBecomeRunning(instances)
WaitForInstancesToAcceptSSHConnection(instances).
return (instances)
}
很好。发生的事情很清晰,程序操作的顺序也很明确。没有白噪声干扰你理解代码和流程。我真的希望最终的代码看起来是这样的。
但实际上,我不能使用同步解决方案,因为每个函数都可能运行很长时间,并且每个函数都需要做诸如:更新UI、监视超时是否超过以及定期重试操作直到成功或超时等操作。简而言之,每个操作都需要在后台进行,以便前台UI线程可以继续进行。
但是,如果我使用BackgroundWorker之类的解决方案,似乎就无法像上面那样拥有易于跟踪的程序逻辑了。相反,我可能会从我的UI线程启动一个背景工作线程来执行第一个函数,然后我的UI线程回到UI,而工作线程在运行。当它完成时,它的“完成”事件处理程序可能会启动下一个Background Worker。当它完成时,它的“完成”事件处理程序可能会启动最后一个Background Worker,以此类推。这意味着您必须“跟随”Done事件处理程序才能了解整个程序流程。
一定有更好的方法,使得a)让我的UI线程响应,b)让我的异步操作能够更新UI,最重要的是c)能够将我的程序表达为一系列连续的步骤(如上所示),以便其他人可以理解生成的代码。
任何建议都会非常感激! 迈克尔
FromCurrentSynchronizationContext
从任务中报告进度的文章。不过要提醒一下,相比这些方法,async
/await
更胜一筹! - Stephen Cleary