以下帖子已成为bit*比预期更长*我对此表示歉意,但也许你会发现阅读起来很有趣,也许你有一个想法可以帮助我:)
我正在开发一个小型应用程序,其 GUI 由许多列表控件组成。每个列表控件都有一个thread与之相关的是永久生产正在添加到列表中的字符串。
允许列表控件由不同线程更新我建了一个扩展的 ObservableCollection that 异步调用其所有业务均以用户界面调度程序效果很好。以下是该类的代码片段,用于举例说明插入操作:
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++;
// ...
}
}
这背后的想法缓冲调用者那是可观察集合不要单独调用操作,而是调用添加调用的方法缓冲调用者这使得调用任务进入其_工作负载列表。这缓冲调用者然后维护两个“内部”线程,它们在_queue- 一个线程从_工作负载列表并将它们放入_queue另一个线程将调用从_queue最后依次调用它们。
所以这只不过是两个要存储的缓冲区待处理的调用任务为了delay他们的实际调用。我正在进一步数数调用任务实际上已经被调用了_出队线程(即长_invoked)以及从其执行中返回的方法的数量(可观察集合称为回到()的方法缓冲调用者当它完成执行时 - 存储在_回多变的。
我的想法是使用 (_invoked - _回)来感受一下workloadUI 调度程序的 - 但令人惊讶的是_pending总是低于 1 或 2。
所以我现在的问题是,虽然我延迟了对 UI 调度程序的方法调用(使用 Thread.Sleep(delay)),但应用程序在一段时间后开始滞后,这反映了 UI 有太多事情需要处理的事实用户输入/输出。
但是 - 这就是我真正想知道的 -_pendingcounter 永远不会达到高值,大多数时候它是 0,即使 UI 已经被冻结。
所以我现在必须找到
(1)一种测量 UI 调度程序工作负载的方法,以确定 UI 调度程序过度工作的点,以及
(2)做一些反对它的事情。
现在非常感谢您阅读到这里,我希望您有任何想法如何在 UI 调度程序上调用任意大量方法而不压倒它。
先感谢您 ...强调文字*强调文字*