Я очень новичок в потоках, поэтому мои мысли и вопросы могут быть немного глупыми :)
Я заполняю элемент управления WinForm
данными из другого потока, поэтому мне нужно вызвать Invoke()
когда я пытаюсь получить доступ к элементу управления.
Если я правильно понимаю, treeView.BeginInvoke(/*some Action()*/)
делает это Action<>()
в основном потоке. Но я " BeginInvoke()
и забываю" этот BeginInvoke()
, поэтому я не могу знать, когда работа действительно выполнена. И даже когда рабочий поток закрывается, и выполнение возвращается в основной поток, я не могу быть уверен, что все BeginInvoke()
завершили выполнение.
Вот почему даже после возвращения в основной поток я не могу управлять с Control
к которому я BeginInvoke()
.
Фактическая проблема - TreeView.ExpandAll()
не работает.
Взгляните на фрагмент кода ниже.
private void btnGetTree_Click(object sender, EventArgs e) {
var treeViewWriter = new Thread(() => UpdateTreeView(new AddXmlNodeArgs(di, null), treeDirectoryContents));
treeViewWriter.Start();
treeViewWriter.Join();
treeDirectoryContents.ExpandAll();
}
// method runs on a worker thread
public static void UpdateTreeView(AddXmlNodeArgs args, TreeView treeView) {
// I will miss details... Here is the code that I run for every new TreeNode:
treeView.UpdateTree((TreeView tree) => {
tree.Nodes[0].Nodes.Add(newTreeNode); // treeView.Nodes[0]...
});
}
// Extension method for TreeView
public static void UpdateTree(this TreeView tree, Action<TreeView> code) {
if (tree.InvokeRequired)
tree.BeginInvoke(code, tree);
else
code.Invoke(tree);
}
Я tree.BeginInvoke()
но я не называю EndInvoke()
нигде. Поэтому я думаю, когда в btnGetTree_Click
выполнение достигает treeDirectoryContents.ExpandAll()
- не все методы Invoke()
завершили свою работу. Вот почему .ExpandAll() doesn't work
.
Поправьте меня, если я ошибаюсь, и, пожалуйста, дайте совет, как решить эту проблему.
Это абсолютно неправильно:
treeViewWriter.Start();
treeViewWriter.Join();
Никогда не называйте Thread.Join из главной темы! потому что эта Join
замораживает приложение, и все эти BeginInvoke
/Invoke
никогда полностью не выполняются, потому что сообщение не обрабатывается.
Вот как работает BeginInvoke()
:
Application.DoEvents()
(или тому подобное, которое всегда вызывается в Application.Run()
)BeginInvoke()
WaitHandle
в IAsyncResult
)EndInvoke()
ожидает такого сигнала (или если IAsyncResult
из BeginInvoke
никогда не хранится, он получает сбор мусора)Итак, снова: вы eiter пишете его чисто управляемым событиями или делаете что-то вроде этого:
private bool done = false;
void click(object, EventArgs) {
thread.Start();
while(!done) Application.DoEvents();
tree.ExpandAll();
}
ADDON: Eihter использует Invoke()
(синхронизированный) и указанный выше цикл с Application.DoEvents()
или используйте BeginInvoke() и вызовите ExpandAll таким же образом (через BeginInvoke() из потока)
ADDON2:
private bool done;
void click(object,EventArgs) {
done = false; // init state
new Thread(work).Start(); // start backgound work
while(!done) Application.DoEvents(); // wait until done
finish(); } // finish the job in main thread
void work() {
Thread.Sleep(100); // do your work
done = true; } // signal done
void finish() {
whatever(); } // called on main thread
void click2(object,EventArgs) {
new Thread(work2).Start(); } // just start the hread
void work2() {
Thread.Sleep(100); // do your work
BeginInvoke(new Action(finish)); } // execute finish() on main thread
Application.DoEvents()
. Поэтому я пытаюсь найти другой способ решить мою проблему
Application.DoEvents()
плох, если вы выполняете тяжелую работу в основном потоке и используете это, чтобы получить хотя бы некоторую отзывчивость. Это совершенно нормально, если вы выполняете тяжелую работу в другом потоке и просто ждете, пока она завершится в таком цикле Application (DoEvents ()). ... но: обычно есть путь чистых событий :)
Создание Action
, которые Invoke
делегат, то BeginInvoke
это действие. Таким образом, у вас будет обратный вызов, который вы можете переместить ExpandAll
в:
if (tree.InvokeRequired)
new Action(() => { tree.Invoke(code, tree); }).BeginInvoke((ar) => {
treeDirectoryContents.ExpandAll();
}, null);
else
code.Invoke(tree);
Обратите внимание, что я заменил ваш оригинальный BeginInvoke
простым Invoke
.
ОБНОВЛЕНИЕ: Поскольку фирда упоминается правильно, потому что основной поток заблокирован внутри метода Join
ожидающий выхода другого потока, выполнение Invoke
на элементах управления приведет к тупику. Итак, теперь, когда ваш ExpandAll
перемещается в обратный вызов, вы должны удалить Join
и все будет хорошо.
Join
больше.
btnGetTree_Click
? Нет необходимости для потока в этом использованииbtnGetTree_Click
которые работают как «производитель / несколько потребителей»