3

下面的帖子比预期的要长一点*我为此道歉,但也许你会觉得它读起来很有趣,也许你有一个想法可以帮助我:)

我正在开发一个小型应用程序,它的 GUI 由许多列表控件组成。每个 List 控件都有一个与之关联的线程,该线程永久地生成要添加到列表中的字符串。

为了允许不同线程更新List 控件,我构建了一个扩展的 ObservableCollection,它异步调用UI 调度程序的所有操作,效果很好。这是该类的代码片段,用于示例插入操作:

public class ThreadSaveObservableCollection<T> : ObservableCollection<T> {

    private int _index;

    private Dispatcher _uiDispatcher;
    private ReaderWriterLock _rwLock;

    // ...

    private bool _insertRegFlag;

    new public void Insert (int index, T item) {

        if (Thread.CurrentThread == _uiDispatcher.Thread) {

            insert_(index, item);
        } else {

            if (_insertRegFlag) { }
            else {

                BufferedInvoker.RegisterMethod(_index + "." + (int)Methods.Insert);
                _insertRegFlag = true;
            }

            BufferedInvoker.AddInvocation(new Invocation<int, T> { Ident = _index + "." + (int)Methods.Insert, Dispatcher = _uiDispatcher, Priority = DispatcherPriority.Normal, Param1 = index, Param2 = item, Method = new Action<int, T>(insert_) });
        }
    }

    private void insert_ (int index, T item) {

        _rwLock.AcquireWriterLock(Timeout.Infinite);

        DateTime timeStampA = DateTime.Now;

        base.Insert(index, item);

        DateTime timeStampB = DateTime.Now;

        BufferedInvoker.Returned(_index + "." + (int)Methods.Insert, timeStampB.Subtract(timeStampA).TotalMilliseconds);

        _rwLock.ReleaseWriterLock();
    }

    // ...
}

为了以一种挂起的调用任务的形式对调用进行建模,我构建了以下内容:

public interface IInvocation {

    string Ident { get; set; }
    void Invoke ();
}

public struct Invocation : IInvocation {

    public string Ident { get; set; }
    public Dispatcher Dispatcher { get; set; }
    public DispatcherPriority Priority { get; set; }
    public Delegate Method { get; set; }

    public void Invoke () {

        Dispatcher.BeginInvoke(Method, Priority, new object[] { });
    }
}

我现在的问题是,由于我在 UI Dispatcher 上调用了大量的方法调用(我有大约 8 到 10 个线程,它们正在永久生成它们添加到列表中的字符串),我的UI 失去了响应用户的能力大约之后的I/O(例如使用鼠标)。大约一分钟后,它完全不接受任何用户交互 30 秒。

为了解决这个问题,我编写了某种缓冲调用程序,它负责缓冲我想要调用到 UI 调度程序上的所有方法调用,然后以受控方式调用它们,例如在调用之间有一些延迟以避免淹没UI 调度程序。

这是一些代码来说明我在做什么(请参阅代码段后的描述):

public static class BufferedInvoker {

    private static long _invoked;
    private static long _returned;
    private static long _pending;
    private static bool _isInbalanced;

    private static List<IInvocation> _workLoad;
    private static Queue<IInvocation> _queue;

    private static Thread _enqueuingThread;
    private static Thread _dequeuingThread;
    private static ManualResetEvent _terminateSignal;
    private static ManualResetEvent _enqueuSignal;
    private static ManualResetEvent _dequeueSignal;

    public static void AddInvocation (IInvocation invocation) {

        lock (_workLoad) {

            _workLoad.Add(invocation);
            _enqueuSignal.Set();
        }
    }

    private static void _enqueuing () {

        while (!_terminateSignal.WaitOne(0, false)) {

            if (_enqueuSignal.WaitOne()) {

                lock (_workLoad) {

                    lock (_queue) {

                        if (_workLoad.Count == 0 || _queue.Count == 20) {

                            _enqueuSignal.Reset();
                            continue;
                        }

                        IInvocation item = _workLoad[0];
                        _workLoad.RemoveAt(0);
                        _queue.Enqueue(item);

                        if (_queue.Count == 1) _dequeueSignal.Set();
                    }
                }
            }
        }
    }

    private static void _dequeuing () {

        while (!_terminateSignal.WaitOne(0, false)) {

            if (_dequeueSignal.WaitOne()) {

                lock (_queue) {

                    if (_queue.Count == 0) {

                        _dequeueSignal.Reset();
                        continue;
                    }

                    Thread.Sleep(delay);

                    IInvocation i = _queue.Dequeue();
                    i.Invoke();

                    _invoked++;
                    _waiting = _triggered - _invoked;
                }
            }
        }
    }

    public static void Returned (string ident, double duration) {

        _returned++;

        // ...
    }
}

这个BufferedInvoker背后的想法是ObservableCollections不会自己调用操作,而是调用BufferedInvoker的AddInvocation方法,该方法将调用任务放入其_workload列表中。然后,BufferedInvoker维护两个在_queue上运行的“内部”线程——一个线程从_workload列表中获取调用并将它们放入_queue,另一个线程将调用从_queue中取出,最后一个接一个地调用它们。

所以这只不过是两个缓冲区来存储挂起的调用任务延迟它们的实际调用。我进一步计算了_dequeuing线程(即 long _invoked )实际调用的调用任务的数量以及从它们的执行中返回的方法的数量( ObservableCollection中的每个方法都调用了Returned()方法BufferedInvoker完成执行时 - 一个存储在_returned变量中的数字。

我的想法是使用 ( _invoked - _returned ) 获取挂起调用的数量,以了解 UI 调度程序的工作量- 但令人惊讶的是_pending总是低于 1 或 2。

所以我现在的问题是,虽然我延迟了对 UI 调度程序的方法调用(使用 Thread.Sleep(delay)),但应用程序在一段时间后开始滞后,这反映了 UI 有太多事情要做来处理用户 I/O。

但是 - 这就是我真正想知道的 - _pending计数器永远不会达到很高的值,即使 UI 已经被冻结,大多数时候它也是 0。

所以我现在必须找到

(1)一种衡量 UI 调度器工作量的方法,以确定 UI 调度器过度工作的点和

(2)做一些反对它的事情。

所以现在非常感谢您阅读到此为止,我希望您有任何想法如何在 UI 调度程序上调用任意数量的方法而不会压倒它..

提前谢谢...强调文字*强调文字*

4

1 回答 1

3

快速看了一下,我注意到你睡觉时是拿着锁的。这意味着在睡觉时没有人可以入队,从而使队列变得无用。

应用程序延迟不是因为队列很忙,而是因为几乎总是持有锁。

我认为你最好删除所有手动实现的队列、锁和监视器,只使用内置的 ConcurrentQueue。每个 UI 控件和线程一个队列,每个队列一个计时器。

无论如何,这是我的建议:

ConcurrentQueue<Item> queue = new ...;

//timer pulls every 100ms or so
var timer = new Timer(_ => {
 var localItems = new List<Item>();
 while(queue.TryDequeue(...)) { localItems.Add(...); }
 if(localItems.Count != 0) { pushToUI(localItems); }
});

//producer pushes unlimited amounts
new Thread(() => { while(true) queue.Enqueue(...); });

简单的。

于 2012-07-04T00:06:14.133 回答