如何将信息从 ThreadPool.QueueUserWorkItem 传递回 UI 线程?
Posted
技术标签:
【中文标题】如何将信息从 ThreadPool.QueueUserWorkItem 传递回 UI 线程?【英文标题】:How do I pass information from a ThreadPool.QueueUserWorkItem back to the UI thread? 【发布时间】:2011-03-29 21:18:16 【问题描述】:我有一个相当简单的线程问题。
我正在编写一个简单的实用程序,它将根据用户定义的参数运行各种 SQL 脚本。
为了保持 UI 响应并提供有关正在执行的脚本状态的反馈,我决定使用 ThreadPool.QueueUserWorkItem
来处理各种脚本的执行(通过 SMO。)
但是,对于如何将 SMO 将返回的输出信息传递回 UI 线程,我感到有些困惑。
对于这个实用程序,我使用 WPF 和 MVVM 进行演示。我在想我会有一个ScriptWorker
类,我可以传递参数和位置以及运行脚本的顺序。
在我运行每个脚本之后,我想以某种方式将结果返回给 UI 线程,以便它更新输出窗口,然后我想让工作人员移动到下一个任务。
我确定这是一个基本问题,但在查看 QueueUserWorkItem
并看到我基本上是通过回调开始工作后,我不确定如何完成我想要完成的工作。
我的假设基于这篇 Microsoft 文章:
http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx
感谢您的信息!
【问题讨论】:
我会看看 BackgroundWorker 类。它使运行后台进程和向 UI 线程发送更新变得容易 【参考方案1】:QueueUserWorkItem
在技术上可以工作,但级别极低。有更简单的方法。
我建议使用 .NET 4.0 的新 Task
功能。它完全符合您的要求,包括将结果或错误条件同步到另一个线程(在本例中为 UI 线程)。
如果 .NET 4.0 不是一个选项,那么我会推荐 BackgroundWorker
(如果您的后台处理不是太复杂),或者像 Hans 提到的异步委托。如果您使用异步委托,则使用 AsyncOperation
类将结果编组回 UI 线程。
Task
选项非常好,因为它非常自然地处理父/子任务。 BackgroundWorker
不能嵌套。另一个考虑因素是取消; Task
和 BackgroundWorker
具有对取消的内置支持,但对于异步委托,您必须自己做。
Task
比BackgroundWorker
稍微复杂一点的唯一地方是正在进行报告。它不像BackgroundWorker
那样简单,但我有一个包装器on my blog 来最小化它。
总结一下,按优先顺序排列:
Task
- 支持正确编组错误、结果概念、取消和父/子嵌套。它的一个缺点是进度报告并不简单(您必须创建另一个任务并将其安排到 UI 线程)。
BackgroundWorker
- 支持正确编组错误、结果概念、取消和进度报告。它的一个缺点是它不支持
父/子嵌套,这限制了它在 API 中的使用,例如,用于业务层。
Delegate.BeginInvoke
和 AsyncOperation
- 支持正确编组错误、结果概念和进度报告。但是,没有内置的取消概念(尽管可以使用volatile bool
手动完成)。它也不支持父/子嵌套。
Delegate.BeginInvoke
和 SynchronizationContext
- 这与选项 (3) 相同,只是它直接使用 SynchronizationContext
。代码稍微复杂一些,但权衡的是支持父/子嵌套。所有其他限制与选项 (3) 相同。
ThreadPool.QueueUserWorkItem
与 AsyncOperation
或 SynchronizationContext
- 支持进度报告的概念。取消遇到与选项(3)相同的问题。错误的编组并不容易(特别是保留堆栈跟踪)。此外,仅当使用SynchronizationContext
而不是AsyncOperation
时,才可能进行父/子嵌套。此外,此选项不支持结果的概念,因此任何返回值都需要作为参数传递。
如您所见,Task
是明显的赢家。除非 .NET 4.0 不是一个选项,否则应该使用它。
【讨论】:
感谢您的详细解答! @Stephen 感谢您的详细解释和细分。这对于理解不同的方式非常有帮助。顺便说一句,您博客的链接似乎已过时。【参考方案2】:This article 有一个您想要的简单示例。
要返回 UI 线程,您需要引用 ISynchronizeInvoke
接口。例如Form
类实现了这个接口。
在伪代码中你可以这样做:
public class MyForm : Form
private OutputControl outputControl;
public void btnClick(...)
// Start a long running process that gives feedback to UI.
var process = new LongRunningProcess(this, outputControl);
ThreadPool.QueueUserWorkItem(process.DoWork);
class LongRunningProcess
// Needs a reference to the interface that marshals calls back to the UI
// thread and some control that needs updating.
public LongRunningProcess(ISynchonizeInvoke invoker,
OutputControl outputControl)
this.invoker = invoker;
this.outputControl = outputControl;
public void DoWork(object state)
// Do long-running job and report progress.
invoker.Invoke(outputControl.Update(...));
请注意,此示例中的OutputControl
是一个控件,因此也实现了ISynchronizeInvoke
接口,因此您也可以选择直接在此控件上调用Invoke
。
上面概述的方法相当低级,但可以让您有很多控制权,尤其是在您希望如何报告进度方面。 BackgroundWorker
为您提供更高级的解决方案,但控制更少。您只能通过无类型的UserState
属性提供进度状态。
【讨论】:
-1:过时的ISynchronizeInvoke
接口没有被继承到 WPF 中,并且在那个 UI 框架中不受支持。以上是关于如何将信息从 ThreadPool.QueueUserWorkItem 传递回 UI 线程?的主要内容,如果未能解决你的问题,请参考以下文章