简体   繁体   English

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

[英]How do I pass information from a ThreadPool.QueueUserWorkItem back to the UI thread?

I have a rather simple threading question.我有一个相当简单的线程问题。

I'm writing a simple utility that will run various SQL scripts based on parameters defined by the user.我正在编写一个简单的实用程序,它将根据用户定义的参数运行各种 SQL 脚本。

In order to keep the UI responsive and provide feedback as to the status of the scripts that are being executed, I've decided that using ThreadPool.QueueUserWorkItem would be appropriate to handle the execution of the various scripts (via SMO.)为了保持 UI 响应并提供有关正在执行的脚本状态的反馈,我决定使用ThreadPool.QueueUserWorkItem来处理各种脚本的执行(通过 SMO)。

However, I'm a bit confused as to how I can relay the output information that SMO will return back to the UI thread.但是,对于如何中继 SMO 将返回到 UI 线程的 output 信息,我有点困惑。

For this utility, I'm using WPF and MVVM for the presentation.对于此实用程序,我使用 WPF 和 MVVM 进行演示。 I'm thinking that I would have a ScriptWorker class that I could pass the parameters and locations and order in which to run the scripts to.我想我会有一个ScriptWorker class,我可以将参数和位置以及运行脚本的顺序传递给它。

After I run each script, I'd like to somehow return the results to the UI thread so that it updates the output window and then I'd like for the worker to move to the next task.在我运行每个脚本后,我想以某种方式将结果返回到 UI 线程,以便它更新 output window 然后我想让工作人员转到下一个任务。

I'm certain this is a basic question, but after looking at QueueUserWorkItem and seeing that I essentially start the work through a callback, I'm unsure how I'd accomplish what I'd like to accomplish.我确定这是一个基本问题,但在查看QueueUserWorkItem并看到我基本上是通过回调开始工作后,我不确定我将如何完成我想完成的工作。

I'm basing my assumptions off of this Microsoft article:我的假设基于这篇 Microsoft 文章:

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

Thanks for the info!谢谢(你的)信息!

QueueUserWorkItem would technically work, but is extremely low-level. QueueUserWorkItem技术上讲可以正常工作,但是级别很低。 There are easier ways. 有更简单的方法。

I recommend using the new Task feature of .NET 4.0. 我建议使用.NET 4.0的新Task功能。 It does exactly what you want, including synchronizing the result or error conditions to another thread (the UI thread, in this case). 它完全可以实现您想要的功能,包括将结果或错误条件同步到另一个线程(在这种情况下为UI线程)。

If .NET 4.0 is not an option, then I'd recommend either BackgroundWorker (if your background processing is not too complex), or asynchronous delegates such as Hans mentioned. 如果不能选择.NET 4.0,那么我建议您使用BackgroundWorker (如果您的后台处理不太复杂)或异步委托(例如Hans)。 If you use async delegates, then use the AsyncOperation class to marshal the results back to the UI thread. 如果使用异步委托,则使用AsyncOperation类将结果AsyncOperation送回UI线程。

The Task option is very nice because it handles parent/child tasks very naturally. Task选项非常好,因为它非常自然地处理父/子任务。 BackgroundWorker can't be nested. BackgroundWorker无法嵌套。 Another consideration is cancellation; 另一个考虑是取消; Task and BackgroundWorker have built-in support for cancellation, but for async delegates you'd have to do your own. TaskBackgroundWorker内置了对取消的支持,但是对于异步委托,您必须自己做。

The only place where Task is a bit more complex than BackgroundWorker is in progress reporting. TaskBackgroundWorker复杂一点的唯一地方是进度报告。 It's not quite as easy as BackgroundWorker , but I have a wrapper on my blog to minimize that. 它不像BackgroundWorker那样容易,但是我在博客上有一个包装器以将其最小化。

To summarize, in order of preference: 总结一下,按照优先顺序:

  1. Task - supports proper marshaling of errors, the concept of a result, cancellation, and parent/child nesting. Task -支持对错误进行适当的封送处理,结果,取消和父/子嵌套的概念。 Its one weakness is that progress reporting isn't simple (you have to create another Task and schedule it to the UI thread). 它的一个缺点是进度报告并不简单(您必须创建另一个Task并将其安排到UI线程中)。
  2. BackgroundWorker - supports proper marshaling of errors, the concept of a result, cancellation, and progress reporting. BackgroundWorker支持正确地处理错误,结果,取消和进度报告的概念。 Its one weakness is that it doesn't support parent/child nesting, and that limits its usage in APIs, eg, for a business layer. 它的一个弱点是它不支持父/子嵌套,并且限制了它在API中的使用,例如用于业务层。
  3. Delegate.BeginInvoke with AsyncOperation - supports proper marshaling of erros, the concept of a result, and progress reporting. 使用AsyncOperation Delegate.BeginInvoke支持正确地整理错误,结果的概念以及进度报告。 However, there is not a built-in concept of cancellation (though it can be done by hand using a volatile bool ). 但是,没有内置的取消概念(尽管可以使用volatile bool手动完成)。 It also does not support parent/child nesting. 它还不支持父/子嵌套。
  4. Delegate.BeginInvoke with SynchronizationContext - this is the same as option (3) except it uses SynchronizationContext directly. 具有SynchronizationContext Delegate.BeginInvoke与选项(3)相同,不同之处在于它直接使用SynchronizationContext The code is slightly more complex, but the tradeoff is that parent/child nesting is supported. 代码稍微复杂一些,但是要权衡的是要支持父/子嵌套。 All other limitations are identical to option (3). 所有其他限制与选项(3)相同。
  5. ThreadPool.QueueUserWorkItem with AsyncOperation or SynchronizationContext - supports the concept of progress reporting. 带有AsyncOperationSynchronizationContext ThreadPool.QueueUserWorkItem支持进度报告的概念。 Cancellation suffers from the same problem as option (3). 取消遭受与选项(3)相同的问题。 Marshaling of errors is not easy (in particular, preserving the stack trace). 封送错误并不容易(特别是保留堆栈跟踪)。 Also, parent/child nesting is only possible if the SynchronizationContext is used instead of AsyncOperation . 另外,仅当使用SynchronizationContext而不是AsyncOperation ,父/子嵌套才可能。 Furthermore, this option does not support the concept of a result, so any return value(s) need to be passed as arguments. 此外,此选项不支持结果的概念,因此任何返回值都需要作为参数传递。

As you can see, Task is the clear winner. 如您所见, Task无疑是赢家。 It should be used unless .NET 4.0 is not an option. 除非不能选择.NET 4.0,否则应使用它。

I know this is old question but I think QueueUserWorkItem comes in handy when you want a quick thread for testing without having async / await poisoning that often comes along with Task.我知道这是一个老问题,但我认为 QueueUserWorkItem 当您想要一个快速线程进行测试而没有任务经常伴随的异步/等待中毒时会派上用场。

Then use Dispatcher.Invoke to get information from the window message queue.然后使用 Dispatcher.Invoke 从窗口消息队列中获取信息。

example for testing an RFID scan without having the actual device:在没有实际设备的情况下测试 RFID 扫描的示例:

        ThreadPool.QueueUserWorkItem(WaitCallback);

        private void WaitCallback(object state)
        {
            while (true)
            {
                string tid = tids[curtid++ % tids.Count()];

                bool orderShowing = Dispatcher.Invoke(() => { return ContentArea.Content == default; });
                if (orderShowing)
                    Dispatcher.Invoke(() => { OnRFIDScan(null, new RFIDArgs { TIDHex = tid }); });
                Thread.Sleep(10000);
            }
        }

This article has a simple example of what you want. 本文提供了您想要的简单示例。

To get back to the UI thread you need a reference to the ISynchronizeInvoke interface. 要返回UI线程,您需要引用ISynchronizeInvoke接口。 The Form class for example implements this interface. 例如, Form类实现了此接口。

In pseudocode you could do something like this: 用伪代码可以执行以下操作:

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(...));
    }
}

Note that the OutputControl in this example is a control and therefore also implements the ISynchronizeInvoke interface so you can also choose to call Invoke directly on this control. 请注意,此示例中的OutputControl是一个控件,因此也实现了ISynchronizeInvoke接口,因此您也可以选择直接在此控件上调用Invoke

The approach sketched above is rather low-level but gives you a lot of control, especially over how you want to report progress. 上面概述的方法相当底层,但是可以为您提供很多控制权,尤其是在您要报告进度的方式上。 BackgroundWorker gives you a more high-level solution but less control. BackgroundWorker为您提供了更高级的解决方案,但控制却更少。 You can only provide progress state via the untyped UserState property. 您只能通过无类型的UserState属性提供进度状态。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM