如何将信息从 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 不能嵌套。另一个考虑因素是取消; TaskBackgroundWorker 具有对取消的内置支持,但对于异步委托,您必须自己做。

TaskBackgroundWorker 稍微复杂一点的唯一地方是正在进行报告。它不像BackgroundWorker 那样简单,但我有一个包装器on my blog 来最小化它。

总结一下,按优先顺序排列:

    Task - 支持正确编组错误、结果概念、取消和父/子嵌套。它的一个缺点是进度报告并不简单(您必须创建另一个任务并将其安排到 UI 线程)。 BackgroundWorker - 支持正确编组错误、结果概念、取消和进度报告。它的一个缺点是它不支持 父/子嵌套,这限制了它在 API 中的使用,例如,用于业务层。 Delegate.BeginInvokeAsyncOperation - 支持正确编组错误、结果概念和进度报告。但是,没有内置的取消概念(尽管可以使用volatile bool 手动完成)。它也不支持父/子嵌套。 Delegate.BeginInvokeSynchronizationContext - 这与选项 (3) 相同,只是它直接使用 SynchronizationContext。代码稍微复杂一些,但权衡的是支持父/子嵌套。所有其他限制与选项 (3) 相同。 ThreadPool.QueueUserWorkItemAsyncOperationSynchronizationContext - 支持进度报告的概念。取消遇到与选项(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 线程?的主要内容,如果未能解决你的问题,请参考以下文章

如何将错误信息从 oracle 传递给访问

如何将用户信息从前端传递到后端

如何将信息从 node.js 传递到 html

如何将有关从通道获取的消息的信息放入 JSON 文件?

如何将信息从 ThreadPool.QueueUserWorkItem 传递回 UI 线程?

如何将信息从 MapKit 注释发送到新的视图控制器