gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 16:23:47 25 4
gpt4 key购买 nike

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

我正在开发一个小应用程序,它的 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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com