协助UI调度处理方法调用的洪流 [英] Assist the UI Dispatcher to handle a flood of method invocations

查看:138
本文介绍了协助UI调度处理方法调用的洪流的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下职位已成为一个的的* *长比预期我对此表示道歉,但也许你会发现它有趣的阅读,也许你有一个想法,以帮助我:)

The following post has become a bit *longer* than expected I apologize for that but maybe you'll find it interesting to read and maybe you have an idea to help me :)

我开发一个小应用程序的GUI由一些列表控件的。每个列表控件具有的线程的关联与为永久生产正在添加到列表中的字符串。

I am developing a small application whose GUI consists of a number of List controls. Each List control has a thread associated with which is permanently producing strings that are being added to the list.

要允许列表控件的是由不同的线程更新的我建立了一个的扩展的ObservableCollection 的是异步调用的所有业务转移到的 UI调度的其中工程精致漂亮。下面是这个类的一个代码片段来examplify插入操作:

To allow the List controls being updated by different threads I built an extended ObservableCollection that asynchronously invokes all its operations to the UI dispatcher which works pretty fine. Here is a code-snippet of that class to examplify the insert-operation:

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

    // ...
}

要在一种未决调用任务的形式调用模型我建了以下内容:

To model the invocation in form of a kind of pending invocation-task I built the following:

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调度的(我有aprox的8到10个线程这是永久的生产,他们添加到他们的名单字符串)我的 UI失去对用户的反应能力我/ O 的(例如用鼠标)aprox的了。 30秒钟时间,不接受后都约一分钟任何用户交互。

My problem now is that because of the enormous amount of method calls I am invoking onto the UI Dispatcher (I have aprox. 8 to 10 threads which are permanently producing strings which they add to their Lists) my UI looses the ability to respond to user I/O (e.g. with the mouse) after aprox. 30 seconds until it doesn't accept any user interaction at all after about a minute.

要面对这个问题,我写了某种缓冲调用的,负责缓冲所有方法调用我想要调​​用到UI调度员然后调用它们在控制的方式如:与为免驱的UI调度的调用之间的一些延迟。

To face this problem I wrote some kind of a buffered invoker that is responsible for buffering all method calls I want to invoke onto the UI dispatcher to then invoke them in a controlled way e.g. with some delay between the invocations to avoid flooding the UI dispatcher.

下面是一些代码来说明我在做什么(请参阅说明之后代码段):

Here is some code to illustrate what I am doing (please see the description after the code-segment):

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 完全不需要对自己的操作,而是调用的 AddInvocation 的方法< STRONG> BufferedInvoker 这使的的调用任务的到它的 _workload 名单。在 BufferedInvoker ,然后维持两个内部上一个 _queue 运行的线程 - 一个线程需要从 _workload 列表中调用,并把它们放到< STRONG> _queue 和其他线程会将调用了 _queue 最后调用它们一个接一个。

The idea behind this BufferedInvoker is that the ObservableCollections don't invoke the operations on their own but instead call the AddInvocation method of the BufferedInvoker which puts the invocation-task into its _workload list. The BufferedInvoker then maintains two 'internal' threads that operate on a _queue - one thread takes invocations from the _workload list and puts them into the _queue and the other thread puts the invocations out of the _queue and finally invokes them one after the other.

所以,这是给的延迟的实际调用不外乎两个缓冲区其他存储的待调用的任务的秩序。我进一步计数的调用任务的数量的已通过 _dequeuing 线程被实际调用(即长的 _invoked )和方法的数已经从他们的执行(内的每个方法返回的的ObservableCollection 调用()返回 BufferedInvoker 当它完成它的执行方法 - 一个存储在 _returned 变量。

So that's nothing else than two buffers to store pending invocation-tasks in order to delay their actual invocation. I am further counting the number of invocation-tasks that have been actually invoked by the _dequeuing thread (i.e. long _invoked) and the number of methods that have been returned from their execution (every method inside the ObservableCollection calls the Returned() method of the BufferedInvoker when it completes its execution - a number that is stored inside the _returned variable.

我的想法是让与( _invoked - _returned )获得的的工作量的用户界面调度的感觉 - 但令人惊讶的 _pending 总是低于1或2

My idea was to get the number of pending invocations with (_invoked - _returned) to get a feeling of the workload of the UI dispatcher - but surprisingly _pending is always below 1 or 2.

所以,我现在的问题是,虽然我拖延方法到UI调度的调用(用Thread.sleep代码(延迟))的应用程序启动时的一些反映后滞后事实UI有太多的事情要做,以处理用户I / O。

So my problem now is that although I am delaying the invocations of methods to the UI dispatcher (with Thread.Sleep(delay)) the application starts to lag after some time which reflects the fact that the UI has too much to do as to handle user I/O.

但是 - 这就是我真的想知道 - 在 _pending 计数器从未到达一个很高的价值,大部分时间是0,即使用户界面已经被冻结。

But - and that is what I am really wondering - the _pending counter never reaches a high value, most of the time it's 0 even if the UI has already been frozen.

所以我现在必须找到

(1)办法来衡量UI调度员的工作量来决定的地步UI调度过度工作,

(1) a way to measure the workload of the UI dispatcher to determine the point where the UI dispatcher is over-worked and

(2)做反对的东西。

所以,现在非常感谢您阅读,直到这一点上,我希望你有任何想法如何调用任意高一些方法上没有压倒它。

So now Thank you very much for reading until this point and I hope you have any ideas how to invoke an arbitrary high number of methods onto the UI dispatcher without overwhelming it..

UI调度预先感谢您... 强调文本的*强调文本*

Thank you in advance ...emphasized text*emphasized text*

推荐答案

被采取快速浏览一下我注意到,你在持有锁睡觉。这意味着,睡觉时没有人可以排队,渲染队列没用。

Having taken a quick look I notice that you sleep with the lock held. This means that while sleeping no one can enqueue, rendering the queue useless.

应用程序不会落后,因为队列忙,但因为持有锁几乎总是如此。

The application does not lag because the queue is busy, but because the lock is held almost always.

我想你会好起来删除所有的手动实现队列和锁和显示器,只需使用内置的ConcurrentQueue。每UI控制线程,每个队列一个定时器一个队列

I think you'll be better off removing all your manually implemented queues and locks and monitors and just use the built-in ConcurrentQueue. One queue per UI control and thread, and one timer per queue.

总之,这里是我的建议:

Anyway, here is what I propose:

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



简单。

Simple.

这篇关于协助UI调度处理方法调用的洪流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆