代码之家  ›  专栏  ›  技术社区  ›  Ian P

如何将信息从threadpool.queueUserWorkItem传递回UI线程?

  •  4
  • Ian P  · 技术社区  · 15 年前

    我有一个相当简单的线程问题。

    我正在编写一个简单的实用程序,它将基于用户定义的参数运行各种SQL脚本。

    为了保持UI的响应性并提供有关正在执行的脚本状态的反馈,我决定使用 ThreadPool.QueueUserWorkItem 适合处理各种脚本的执行(通过SMO)。

    但是,对于如何中继SMO将返回到UI线程的输出信息,我有点困惑。

    对于这个实用程序,我使用wpf和mvvvm进行演示。我在想我会有一个 ScriptWorker 类,我可以传递参数、位置和运行脚本的顺序。

    在运行每个脚本之后,我想以某种方式将结果返回到UI线程,以便它更新输出窗口,然后我希望工作人员转到下一个任务。

    我确定这是一个基本问题,但在看了之后 QueueUserWorkItem 鉴于我基本上是通过回调开始工作的,我不确定如何完成我想要完成的工作。

    我的假设基于这篇微软文章:

    http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx

    谢谢你的信息!

    2 回复  |  直到 15 年前
        1
  •  8
  •   Stephen Cleary    15 年前

    QueueUserWorkItem 技术上可行,但水平极低。有更简单的方法。

    我建议使用新的 Task .NET 4.0的功能。它完全按照您的要求执行,包括将结果或错误条件同步到另一个线程(在本例中是UI线程)。

    如果.NET 4.0不是一个选项,那么我建议 BackgroundWorker (如果您的后台处理不太复杂),或者异步委托(如hans)。如果使用异步委托,则使用 AsyncOperation 类将结果封送回UI线程。

    这个 任务 选项非常好,因为它非常自然地处理父/子任务。 背景工作者 不能嵌套。另一个考虑因素是取消; 任务 背景工作者 有内置的取消支持,但对于异步委托,您必须自己做。

    唯一的地方 任务 背景工作者 正在进行报告。这可不像 背景工作者 但是我有包装纸 on my blog 把这个最小化。

    总结如下,按优先顺序:

    1. 任务 -支持正确的错误封送、结果概念、取消和父/子嵌套。它的一个缺点是进度报告并不简单(您必须创建另一个任务并将其调度到UI线程)。
    2. 背景工作者 -支持正确编组错误、结果概念、取消和进度报告。它的一个弱点是它不支持 父/子嵌套,这限制了它在API中的使用,例如,对于业务层。
    3. Delegate.BeginInvoke 具有 异步操作 -支持正确编组错误、结果概念和进度报告。但是,没有内置的取消概念(尽管可以使用 volatile bool )它也不支持父/子嵌套。
    4. 委托.begininvoke 具有 SynchronizationContext -这与选项(3)相同,只是它使用 行绪 直接。代码稍微复杂一些,但需要权衡的是,支持父/子嵌套。所有其他限制与选项(3)相同。
    5. ThreadPool.QueueUserWorkItem 具有 异步操作 行绪 -支持进度报告的概念。取消与选项(3)的问题相同。错误的封送不容易(特别是保存堆栈跟踪)。此外,只有在 行绪 使用而不是 异步操作 . 此外,此选项不支持结果的概念,因此需要将任何返回值作为参数传递。

    正如你所看到的, 任务 显然是赢家。除非.NET 4.0不是选项,否则应使用它。

        2
  •  -1
  •   Ronald Wildenberg    15 年前

    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 在本例中是一个控件,因此也实现了 同步调用 接口,以便您也可以选择调用 Invoke 直接在此控件上。

    上面概述的方法是相当低的级别,但可以给您很大的控制权,特别是在您希望如何报告进度方面。 BackgroundWorker 给你一个更高层次的解决方案,但更少的控制。只能通过非类型化提供进度状态 UserState 财产。