将对象移动到单独线程的最佳实践

时间:2010-09-03 16:29:51

标签: c# wpf multithreading user-interface singleton

我们有一个超声机器应用程序当前实现,其中Ultrasound对象是在UI的线程上创建的。 Singleton实现在这里会很好,但无论如何都不是。

最近,设定方法发生了变化,使得它们自动停止并重新启动超声波机器,根据机器的状态,可能需要10-100毫秒。对于大多数情况,这不是一个问题,但它仍然导致UI线程阻塞100毫秒。此外,这些方法不是线程安全的,必须在初始化对象的同一线程上调用。

这个现在造成的最大问题是UI中没有响应的按钮,尤其是在滑动条时可能会尝试多次更新变量的滑块。因此,滑块尤其会因为通过数据绑定事件进行多次设置调用而导致断断续续和更新。

创建专门用于创建和处理此Ultrasound对象的线程有什么好方法,它将在应用程序的生命周期内持续存在?

当前的临时解决方法涉及产生一个Timer,并在我们检测到滑块没有移动200ms后调用参数更新,但是必须为每个滑块实现一个Timer,这似乎是一个非常混乱的解决方案解决了无响应的滑块,但偶尔会阻止UI线程。

3 个答案:

答案 0 :(得分:4)

对GUI进行编程真的很棒的一点是,你不必担心多个线程会为你搞砸(假设你有CheckForIllegalCrossThreadCalls = true,就像你应该的那样)。它都是单线程的,通过消息泵(队列)来逐个处理传入的消息。

由于您已经表明需要同步未编写为线程安全的方法调用(完全可以理解),因此您没有理由无法实现自己的消息泵来处理用你的超声波物体。

一个天真的,非常简单的版本可能看起来像这样(BlockingCollection<T>类很棒,如果你在.NET 4.0上或安装了Rx extensions;否则,你可以使用普通的香草Queue<T>并自己锁定)。 警告 :这只是我刚才拼凑的快速骨架;我对它的稳健性甚至是正确性都没有任何承诺。

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

然后会发生什么:每次你想以某种方式与你的Ultrasound对象进行交互时,你可以通过这个消息泵,通过调用Submit并传递一些与你的Ultrasound对象一起使用的动作来实现。然后,Ultrasound对象同时接收发送给它的所有消息(我的意思是,一次一个),同时在自己的非GUI线程上运行。

答案 1 :(得分:3)

您应该维护一个专用的UltraSound线程,该线程创建UltraSound对象,然后侦听来自其他线程的回调。

您应该维护一个线程安全的委托队列,让UltraSound线程重复执行并删除队列中的第一个委托。

这样,UI线程可以将操作发布到队列,然后由UltraSound线程异步执行。

答案 2 :(得分:0)

我不确定我是否完全理解设置,但这是我尝试解决方案:

让滑块的事件处理程序检查最后一个事件时间,并在处理用户调整之前等待50ms(仅处理最新值)。

然后让一个线程使用while循环并等待来自GUI的AutoResetEvent触发器。然后它会创建对象并设置它吗?