次の投稿は予想よりも少し*長く*なってしまいました。
GUI が多数のリスト コントロールで構成される小さなアプリケーションを開発しています。各 List コントロールには、リストに追加される文字列を永続的に生成するスレッドが関連付けられています。
List コントロールをさまざまなスレッドで更新できるようにするために、拡張された ObservableCollectionを構築しました。この拡張 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 (例: マウスを使用) 約 1 分後にユーザーの操作をまったく受け付けなくなるまで 30 秒。
この問題に直面するために、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の背後にある考え方は、ObservableCollectionが独自に操作を呼び出すのではなく、呼び出しタスクをその_workloadリストに入れるBufferedInvokerのAddInvocationメソッドを呼び出すというものです。次に、BufferedInvokerは、 _queueで動作する 2 つの「内部」スレッドを維持します。1つのスレッドは、_workloadリストから呼び出しを取得して_queueに入れ、もう 1 つのスレッドは、呼び出しを_queueから出して、最後に順番に呼び出します。
したがって、実際の呼び出しを遅らせるために、保留中の呼び出しタスクを格納するための 2 つのバッファーに他なりません。さらに、 _dequeuingスレッド (つまり long _invoked )によって実際に呼び出された呼び出しタスクの数と、それらの実行から返されたメソッドの数 ( ObservableCollection内のすべてのメソッドが、オブジェクトのReturned()メソッドを呼び出す) を数えています。実行が完了したときのBufferedInvoker - _returned変数内に格納されている数値。
UI ディスパッチャーの作業負荷を把握するために ( _invoked - _returned )で保留中の呼び出しの数を取得するというのが私の考えでしたが、驚くべきことに _pendingは常に 1 または 2 未満です。
したがって、私の問題は、UIディスパッチャーへのメソッドの呼び出しを遅らせているにもかかわらず(Thread.Sleep(delay)を使用)、しばらくするとアプリケーションが遅れ始め、UIが処理するためにやるべきことが多すぎるという事実を反映していることです。ユーザー I/O。
しかし、私が本当に疑問に思っているのは、_pendingカウンターが高い値に達することはなく、ほとんどの場合、UI が既にフリーズされていても 0 です。
だから私は今見つけなければならない
(1) UI ディスパッチャのワークロードを測定して、UI ディスパッチャが過負荷になっているポイントを特定する方法。
(2)それに対して何かをする。
ここまで読んでいただき、ありがとうございました。UI ディスパッチャを圧倒することなく、任意の多数のメソッドを呼び出す方法について何かアイデアがあれば幸いです。
よろしくお願いします ...強調テキスト*強調テキスト*