Как убедиться, что асинхронный метод закончил работу?

1

Я очень новичок в потоках, поэтому мои мысли и вопросы могут быть немного глупыми :)

Я заполняю элемент управления 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.

Поправьте меня, если я ошибаюсь, и, пожалуйста, дайте совет, как решить эту проблему.

  • 2
    Почему вы создаете поток, а затем ждете его окончания в btnGetTree_Click ? Нет необходимости для потока в этом использовании
  • 0
    @L.BЯ разместил только тот код, который вам нужен, чтобы понять мою проблему. У меня есть несколько потоков в btnGetTree_Click которые работают как «производитель / несколько потребителей»
Показать ещё 2 комментария
Теги:
multithreading
winforms
treeview

2 ответа

1
Лучший ответ

Это абсолютно неправильно:

treeViewWriter.Start();
treeViewWriter.Join();

Никогда не называйте Thread.Join из главной темы! потому что эта Join замораживает приложение, и все эти BeginInvoke/Invoke никогда полностью не выполняются, потому что сообщение не обрабатывается.

Вот как работает BeginInvoke():

  1. Он отправляет некоторый WM_USER (или тому подобное) в контур сообщения.
  2. Основной поток выдает это сообщение в Application.DoEvents() (или тому подобное, которое всегда вызывается в Application.Run())
  3. Основной поток выполняет делегат, переданный BeginInvoke()
  4. Основная нить сигнализирует о завершении выполнения (WaitHandle в IAsyncResult)
  5. 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
  • 0
    вчера я прочитал отличный пост о том, почему вы не должны использовать Application.DoEvents() . Поэтому я пытаюсь найти другой способ решить мою проблему
  • 1
    Application.DoEvents() плох, если вы выполняете тяжелую работу в основном потоке и используете это, чтобы получить хотя бы некоторую отзывчивость. Это совершенно нормально, если вы выполняете тяжелую работу в другом потоке и просто ждете, пока она завершится в таком цикле Application (DoEvents ()). ... но: обычно есть путь чистых событий :)
Показать ещё 11 комментариев
0

Создание 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 и все будет хорошо.

  • 1
    Invoke () + Thread.Join () = DEADLOCK;) (фоновый поток ожидает в Invoke (), основной поток заблокирован в Thread.Join () не перекачивает сообщения)
  • 0
    Вы совершенно правы. Я думаю , что нет никакой необходимости Join больше.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню