开发者

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.

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.)

However, I'm a bit confused as to how I can relay the output information that SMO will开发者_Go百科 return back to the UI thread.

For this utility, I'm using WPF and MVVM for the presentation. 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.

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.

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.

I'm basing my assumptions off of this Microsoft article:

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

Thanks for the info!


QueueUserWorkItem would technically work, but is extremely low-level. There are easier ways.

I recommend using the new Task feature of .NET 4.0. It does exactly what you want, including synchronizing the result or error conditions to another thread (the UI thread, in this case).

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. If you use async delegates, then use the AsyncOperation class to marshal the results back to the UI thread.

The Task option is very nice because it handles parent/child tasks very naturally. BackgroundWorker can't be nested. Another consideration is cancellation; Task and BackgroundWorker have built-in support for cancellation, but for async delegates you'd have to do your own.

The only place where Task is a bit more complex than BackgroundWorker is in progress reporting. It's not quite as easy as BackgroundWorker, but I have a wrapper on my blog to minimize that.

To summarize, in order of preference:

  1. Task - supports proper marshaling of errors, the concept of a result, cancellation, and parent/child nesting. Its one weakness is that progress reporting isn't simple (you have to create another Task and schedule it to the UI thread).
  2. BackgroundWorker - supports proper marshaling of errors, the concept of a result, cancellation, and progress reporting. Its one weakness is that it doesn't support parent/child nesting, and that limits its usage in APIs, e.g., for a business layer.
  3. Delegate.BeginInvoke with AsyncOperation - supports proper marshaling of erros, the concept of a result, and progress reporting. However, there is not a built-in concept of cancellation (though it can be done by hand using a 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. The code is slightly more complex, but the tradeoff is that parent/child nesting is supported. All other limitations are identical to option (3).
  5. ThreadPool.QueueUserWorkItem with AsyncOperation or SynchronizationContext - supports the concept of progress reporting. Cancellation suffers from the same problem as option (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. 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. It should be used unless .NET 4.0 is not an option.


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. The Form class for example implements this interface.

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.

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. You can only provide progress state via the untyped UserState property.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜