我有简单的异步按钮和async / await功能。当我点击我的按钮时,我的UI会冻结。谁能向我解释我做错了什么?
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
LabelCounter.Text = "Running"
LabelCounter.Text = await ComputeText()
End Sub
和这个功能:
Private Async Function ComputeText() As Task(Of String)
Dim result as string = Await Task.Run(Function()
'Thread.Sleep(2000)
Dim i as Integer = 0
While i <> 100000
i += 1
'block as other staff can access it as well from outside
SyncLock thisLock
LabelCounter.Invoke(Sub()
LabelCounter.Text = i
End Sub)
End SyncLock
End While
Return "Done"
End Function)
Return result
End Function
答案 0 :(得分:0)
当我点击我的按钮时,我的UI会冻结
正如其他人所说,即使您正在使用Async
/ Await
(这是正确的),问题仍然是Invoke
。
Invoke
将中断UI线程并告诉它做某事(在这种情况下,更新标签)。问题是后台线程正在尽可能快地中断UI 100,000次。
第二个难题是UI消息优先。 WM_PAINT
(即#34;需要更改屏幕的重绘部分)的优先级低于&#34;运行此任意代码&#34;信息。因此,用户界面实际上没有时间进行刷新,因为后台Invoke
请求具有优先权,并且它们进入的速度太快。
我建议使用标准IProgress<T>
方法报告进度,而不是直接更新标签和使用Invoke
。这样做的好处是可以将UI显示与程序逻辑分开(在后台线程中运行);此外,它使用与框架无关的方法(IProgress<T>
),而不是将您的逻辑特定地绑定到WinForms(Invoke
)。
唯一真正的解决方案是限制后台更新。您可以在后台逻辑本身中执行此操作(即,仅每1000个周期左右发布一次更新),但这并不理想,因为1)它是您的业务逻辑中解决的UI问题; 2)它可以根据系统使用情况,CPU功率等随时间变化
因此,我建议让后台逻辑总是发送更新(使用IProgress<T>.Report
),并让IProgress<T>
实现限制它们。这最容易通过Rx完成。 Lee Campbell有blog post on the subject(这是更合适的解决方案),我有一个down-and-dirty git-er-done approach in a gist(尚未更新为更合适,但设置起来很容易)预先限制的进展。)