以下帖子比预期的稍微长了一些,我为此道歉,但也许您会发现它有趣,也许您有帮助我的想法。
我正在开发一个小应用程序,其GUI由多个列表控件组成。每个列表控件都有一个与之关联的线程,该线程永久地生成字符串并将其添加到列表中。
为了允许不同线程更新列表控件,我构建了一个扩展的ObservableCollection,它异步调用所有操作到UI调度程序,这很好地解决了问题。以下是该类的代码片段,以插入操作为例:
为了模拟待处理调用任务的调用形式,我创建了以下内容:
我的问题是因为我在UI Dispatcher上调用了大量的方法(大约有8到10个线程不断地生产字符串,然后将它们添加到它们的列表中),所以我的UI失去了响应用户I/O(例如使用鼠标)的能力,大约30秒后就无法接受任何用户交互,一分钟后完全无法响应。
为了解决这个问题,我编写了一种缓冲调用程序,负责缓存所有我想要调用到UI调度程序的方法调用,然后以受控方式进行调用,例如在调用之间有些延迟,以避免淹没UI调度程序。
下面是一些代码来说明我的做法(请参见代码段后的描述)。
这个 BufferedInvoker 的想法是,ObservableCollections 不会自己调用操作,而是调用 BufferedInvoker 的 AddInvocation 方法,将 invocation-task 放入其 _workload 列表中。然后,BufferedInvoker 维护两个“内部”线程,这些线程在一个 _queue 上运作 - 一个线程从 _workload 列表中获取调用并将它们放入 _queue 中,另一个线程将调用从 _queue 中取出,并依次进行调用。
因此,这不过是两个缓冲区,用于存储 待处理的调用任务,以 延迟 它们的实际调用。我还统计了由 _dequeuing 线程实际调用的 调用任务 数量(即长整型 _invoked)和已经从其执行中返回的方法数量(每个 ObservableCollection 中的方法在完成执行时调用 BufferedInvoker 的 Returned() 方法 - 这个数字存储在 _returned 变量中)。
我的想法是通过 (_invoked - _returned) 获取待处理调用的数量,以感受 UI 调度程序的 工作量 - 但令人惊讶的是,_pending 总是低于1或2。
所以我的问题现在是,尽管我正在延迟将方法调用到 UI 调度程序(使用 Thread.Sleep(delay)),但应用程序在一段时间后开始滞后,这反映了 UI 处理用户 I/O 的负担过重。
但是 - 这就是我真正想知道的 - 即使 UI 已经被冻结,_pending 计数器也从未达到高值,大多数时候它都是0。
所以现在我必须找到: (1) 测量 UI 调度程序的工作量以确定 UI 调度程序超负荷的点; (2) 对此采取措施。
非常感谢您阅读到这里,希望您有任何想法如何在 UI 调度程序上调用任意数量的方法而不会使其过载。
我正在开发一个小应用程序,其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();
}
// ...
}
为了模拟待处理调用任务的调用形式,我创建了以下内容:
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 方法,将 invocation-task 放入其 _workload 列表中。然后,BufferedInvoker 维护两个“内部”线程,这些线程在一个 _queue 上运作 - 一个线程从 _workload 列表中获取调用并将它们放入 _queue 中,另一个线程将调用从 _queue 中取出,并依次进行调用。
因此,这不过是两个缓冲区,用于存储 待处理的调用任务,以 延迟 它们的实际调用。我还统计了由 _dequeuing 线程实际调用的 调用任务 数量(即长整型 _invoked)和已经从其执行中返回的方法数量(每个 ObservableCollection 中的方法在完成执行时调用 BufferedInvoker 的 Returned() 方法 - 这个数字存储在 _returned 变量中)。
我的想法是通过 (_invoked - _returned) 获取待处理调用的数量,以感受 UI 调度程序的 工作量 - 但令人惊讶的是,_pending 总是低于1或2。
所以我的问题现在是,尽管我正在延迟将方法调用到 UI 调度程序(使用 Thread.Sleep(delay)),但应用程序在一段时间后开始滞后,这反映了 UI 处理用户 I/O 的负担过重。
但是 - 这就是我真正想知道的 - 即使 UI 已经被冻结,_pending 计数器也从未达到高值,大多数时候它都是0。
所以现在我必须找到: (1) 测量 UI 调度程序的工作量以确定 UI 调度程序超负荷的点; (2) 对此采取措施。
非常感谢您阅读到这里,希望您有任何想法如何在 UI 调度程序上调用任意数量的方法而不会使其过载。