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

时间:2010-08-14 16:15:15

标签: c# wpf multithreading .net-3.5

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

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

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

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

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

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

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

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

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

感谢您的信息!

2 个答案:

答案 0 :(得分:8)

QueueUserWorkItem在技术上可行,但是极低级别。有更简单的方法。

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

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

Task选项非常好,因为它非常自然地处理父/子任务。 BackgroundWorker无法嵌套。另一个考虑是取消; TaskBackgroundWorker内置支持取消,但对于异步代理,您必须自己做。

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

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

  1. Task - 支持正确编组错误,结果概念,取消和父/子嵌套。它的一个弱点是进度报告并不简单(您必须创建另一个任务并将其安排到UI线程)。
  2. BackgroundWorker - 支持正确的错误编组,结果概念,取消和进度报告。它的一个弱点是它不支持 父/子嵌套,这限制了它在API中的使用,例如,用于业务层。
  3. Delegate.BeginInvokeAsyncOperation - 支持正确编组错误,结果概念和进度报告。但是,没有内置的取消概念(尽管可以使用volatile bool手动完成)。它也不支持父/子嵌套。
  4. Delegate.BeginInvokeSynchronizationContext - 除了直接使用SynchronizationContext之外,它与选项(3)相同。代码稍微复杂一些,但权衡是支持父/子嵌套。所有其他限制与选项(3)相同。
  5. ThreadPool.QueueUserWorkItem AsyncOperationSynchronizationContext - 支持进度报告的概念。取消遇到与选项(3)相同的问题。错误的编组并不容易(特别是保留堆栈跟踪)。此外,只有在使用SynchronizationContext而不是AsyncOperation时才能进行父/子嵌套。此外,此选项不支持结果的概念,因此任何返回值都需要作为参数传递。
  6. 如您所见,Task是明显的赢家。除非.NET 4.0不是一个选项,否则应该使用它。

答案 1 :(得分:-1)

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属性提供进度状态。