C# 协助UI调度器处理大量的方法调用

C# 协助UI调度器处理大量的方法调用,c#,wpf,multithreading,dispatcher,method-invocation,C#,Wpf,Multithreading,Dispatcher,Method Invocation,下面的帖子比预期的要长一点,对此我深表歉意,但也许你会觉得读起来很有趣,也许你有办法帮我:) 我正在开发一个小应用程序,它的GUI由许多列表控件组成。每个列表控件都有一个与之关联的线程,该线程将永久生成添加到列表中的字符串 为了允许列表控件由不同的线程更新,我构建了一个扩展的ObservableCollection,它异步地将其所有操作调用到UI调度程序,运行得非常好。下面是该类的代码片段,用于示例插入操作: public class ThreadSaveObservableCollection

下面的帖子比预期的要长一点,对此我深表歉意,但也许你会觉得读起来很有趣,也许你有办法帮我:)

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

为了允许列表控件由不同的线程更新,我构建了一个扩展的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();
    }

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

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

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

公共静态类BufferedInvoker{
调用私有静态long_;
返回私有静态long_;
私有静态长挂起;
私有静态布尔不平衡;
私有静态列表\u工作负载;
私有静态队列;
私有静态线程_enqueuingThread;
私有静态线程_dequeingthread;
专用静态手动复位事件_terminateSignal;
专用静态手动复位事件_enqueuSignal;
专用静态手动复位事件_出列信号;
公共静态void AddInvocation(IIInvocation调用){
锁定(_工作负载){
_添加(调用);
_enqueuSignal.Set();
}
}
私有静态void _排队(){
while(!\u terminateSignal.WaitOne(0,false)){
if(_enqueuSignal.WaitOne()){
锁定(_工作负载){
锁定(_队列){
如果(_workLoad.Count==0 ||_queue.Count==20){
_enqueuSignal.Reset();
继续;
}
IInvocation项=_工作量[0];
_工作量。移除(0);
_排队。排队(项目);
如果(_queue.Count==1)_dequeueSignal.Set();
}
}
}
}
}
私有静态void _出列(){
while(!\u terminateSignal.WaitOne(0,false)){
if(_dequeueSignal.WaitOne()){
锁定(_队列){
如果(_queue.Count==0){
_dequeueSignal.Reset();
继续;
}
睡眠(延迟);
IInvocation i=_queue.Dequeue();
i、 调用();
_调用++;
_等待=_触发-_调用;
}
}
}
}
返回的公共静态void(字符串标识,双持续时间){
_返回++;
// ...
}
}
这个BufferedInvoker背后的思想是可观察集合不会自己调用操作,而是调用BufferedInvokerAddInvocation方法,该方法将调用任务放入其\u工作负载列表中。然后,BufferedInvoker维护两个在\u队列上运行的“内部”线程——一个线程从\u工作负载列表中获取调用并将其放入\u队列,另一个线程将调用从\u队列中取出,最后逐个调用另一个

所以只有两个缓冲区来存储挂起的调用任务,以延迟它们的实际调用。我进一步计算了\u出列线程实际调用的调用任务数(即长\u调用),以及从执行中返回的方法数(ObservableCollection中的每个方法调用返回的()完成执行时的BufferedInvoker方法-存储在\u返回的变量中的数字

我的想法是通过(\u invoked-\u returned)获得挂起调用的数量,以了解UI调度程序的工作负载,但令人惊讶的是,\u pending始终低于1或2

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

但是-这就是我真正想知道的-挂起的\u计数器从来没有达到一个高值,大多数时候它是0,即使UI已经冻结

所以我现在必须找到

(1)一种测量UI调度程序工作负载的方法,用于确定UI调度程序工作过度的点,以及

(2)做点什么反对它

所以现在非常感谢您阅读到这一点,我希望您对如何调用任意多个方法有任何想法
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[] { });
    }
}
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++;

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