c# - 协助 UI Dispatcher 处理大量的方法调用

标签 c# wpf multithreading dispatcher method-invocation

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

我正在开发一个小应用程序,它的 GUI 由许多 List 控件组成。每个 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 调度程序上调用的方法调用(我有大约 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++;

        // ...
    }
}

这背后的想法缓冲调用者 ObservableCollections 不要自己调用操作,而是调用 添加调用 的方法缓冲调用者 它将调用任务放入其 _工作量列表。 缓冲调用者 然后维护两个在 上运行的“内部”线程_队列 - 一个线程从 获取调用_工作量列出并将它们放入 _队列另一个线程将调用置于 之外_队列最后一个接一个地调用它们。

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

我的想法是通过 ( _invoked - _returned ) 获取待处理调用的数量,以了解 UI 调度程序的工作负载 - 但令人惊讶的是 _待定总是低于 1 或 2。

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

但是 - 这就是我真正想知道的 - _待定计数器永远不会达到很高的值,即使 UI 已经被卡住,大部分时间它也是 0。

所以我现在必须找到

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

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

所以现在非常感谢您阅读到这里,我希望您有任何想法如何在 UI 调度程序上调用任意数量的方法而不会使它不堪重负。

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

最佳答案

快速浏览后,我注意到你 sleep 时拿着锁。这意味着在休眠时没有人可以入队,从而使队列无用。

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

我认为您最好删除所有手动实现的队列、锁和监视器,而只使用内置的 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(...); });

简单的。

关于c# - 协助 UI Dispatcher 处理大量的方法调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11250947/

相关文章:

c# - 为什么需要在客户端项目中引用 EntityFramework.dll 来使 DbContext IDisposable?

c# - 从 C# 中的 Object 继承是多余的吗?

WPF : Refresh/Update control on Window load

wpf - 部署桌面 WPF/SQLite 应用程序

java - 方法减慢执行 paintComponent()

java - 消费者池的自动缩放

c# - 如何配置 Ninject 与 ServiceStack.net 一起工作

c# - 将 .txt 文件中错误的行添加到列表的最简单方法

wpf - 如何在组合框中将项目设置为选中

.net - WPF 数据绑定(bind)线程安全吗?