开发者

Best practices for moving objects to a separate thread

We have an implementation for an Ultrasound machine application current where the Ultrasound object is created on the UI's thread. A Singleton implementation would have been good here, but regardless, isn't.

Recently, the set methods changed such that they automatically stop and restart the ultrasound machine, which can take between 10-100ms depending on the state of the machine. For most cases, this isn't too bad of a problem, however it's still causing t开发者_开发知识库he UI thread to block for 100ms. Additionally, these methods are not thread-safe and must be called on the same thread where the object was initialized.

This largest issue this is now causing is unresponsive buttons in the UI, especially sliders which may try to update variables many times as you slide the bar. As a result, sliders especially will stutter and update very slowly as it makes many set calls through databound propeties.

What is a good way to create a thread specifically for the creation and work for this Ultrasound object, which will persist through the lifetime of the application?

A current temporary workaround involves spawning a Timer, and invoking a parameter update once we have detected the slider hasn't moved for 200ms, however a Timer would then have to be implemented for every slider and seems like a very messy solution which solves unresponsive sliders, but still blocks the UI thread occasionally.


One thing that's really great about programming the GUI is that you don't have to worry about multiple threads mucking things up for you (assuming you've got CheckForIllegalCrossThreadCalls = true, as you should). It's all single-threaded, operating by means of a message pump (queue) that processes incoming messages one-by-one.

Since you've indicated that you need to synchronize method calls that are not written to be thread-safe (totally understandable), there's no reason you can't implement your own message pump to deal with your Ultrasound object.

A naive, very simplistic version might look something like this (the BlockingCollection<T> class is great if you're on .NET 4.0 or have installed Rx extensions; otherwise, you can just use a plain vanilla Queue<T> and do your own locking). Warning: this is just a quick skeleton I've thrown together just now; I make no promises as to its robustness or even correctness.

class MessagePump<T>
{
    // In your case you would set this to your Ultrasound object.
    // You could just as easily design this class to be "object-agnostic";
    // but I think that coupling an instance to a specific object makes it clearer
    // what the purpose of the MessagePump<T> is.
    private T _obj;

    private BlockingCollection<Action<T>> _workItems;
    private Thread _thread;

    public MessagePump(T obj)
    {
        _obj = obj;

        // Note: the default underlying data store for a BlockingCollection<T>
        // is a FIFO ConcurrentQueue<T>, which is what we want.
        _workItems = new BlockingCollection<Action<T>>();

        _thread = new Thread(ProcessQueue);
        _thread.IsBackground = true;
        _thread.Start();
    }

    public void Submit(Action<T> workItem)
    {
        _workItems.Add(workItem);
    }

    private void ProcessQueue()
    {
        for (;;)
        {
            Action<T> workItem = _workItems.Take();
            try
            {
                workItem(_obj);
            }
            catch
            {
                // Put in some exception handling mechanism so that
                // this thread is always running. One idea would be to
                // raise an event containing the Exception object on a
                // threadpool thread. You definitely don't want to raise
                // the event from THIS thread, though, since then you
                // could hit ANOTHER exception, which would defeat the
                // purpose of this catch block.
            }
        }
    }
}

Then what would happen is: every time you want to interact with your Ultrasound object in some way, you do so through this message pump, by calling Submit and passing in some action that works with your Ultrasound object. The Ultrasound object then receives all messages sent to it synchronously (by which I mean, one at a time), while operating on its own non-GUI thread.


You should maintain a dedicated UltraSound thread, which creates the UltraSound object and then listens for callbacks from other threads.

You should maintain a thread-safe queue of delegates and have the UltraSound thread repeatedly execute and remove the first delegate in the queue.

This way, the UI thread can post actions to the queue, which will then be executed asynchronously by the UltraSound thread.


I'm not sure I fully understand the setup, but here is my attempt at a solution:

How about having the event handler for the slider check the last event time, and wait for 50ms before processing a user adjustment (only process the most recent value).

Then have a thread using a while loop and waiting on an AutoResetEvent trigger from the GUI. It would then create the object and set it?

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜