我有一个相当简单的线程问题。
我正在编写一个简单的实用程序,它将根据用户定义的参数运行各种SQL脚本。
为了保持UI响应并提供有关正在执行的脚本状态的反馈,我决定使用ThreadPool.QueueUserWorkItem
适合处理各种脚本的执行(通过SMO) 。)
但是,我对如何中继SMO将返回UI线程的输出信息感到困惑。
对于这个实用程序,我正在使用WPF和MVVM进行演示。我想我会有一个ScriptWorker
类,我可以传递参数和位置以及运行脚本的顺序。
运行每个脚本后,我想以某种方式将结果返回给UI线程,以便更新输出窗口,然后我希望工作人员转到下一个任务。
我确定这是一个基本问题,但在看了QueueUserWorkItem
并看到我基本上是通过回调开始工作之后,我不确定我是如何完成我想要完成的工作
我的假设是基于这篇微软文章:
http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx
感谢您的信息!
答案 0 :(得分:8)
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
- 除了直接使用SynchronizationContext
之外,它与选项(3)相同。代码稍微复杂一些,但权衡是支持父/子嵌套。所有其他限制与选项(3)相同。ThreadPool.QueueUserWorkItem
AsyncOperation
或SynchronizationContext
- 支持进度报告的概念。取消遇到与选项(3)相同的问题。错误的编组并不容易(特别是保留堆栈跟踪)。此外,只有在使用SynchronizationContext
而不是AsyncOperation
时才能进行父/子嵌套。此外,此选项不支持结果的概念,因此任何返回值都需要作为参数传递。如您所见,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
属性提供进度状态。