В C # подождать mainthread, продолжая обрабатывать обновления пользовательского интерфейса? (.NET 2.0 CF) - PullRequest
3 голосов
/ 11 декабря 2008

В противном случае я хочу заблокировать выполнение кода в главном потоке, в то же время позволяя отображать изменения пользовательского интерфейса.

Я попытался придумать упрощенную примерную версию того, что я пытаюсь сделать; и это лучшее, что я мог придумать. Очевидно, это не демонстрирует желаемого поведения, иначе я бы не стал публиковать вопрос. Я просто надеюсь, что это даст некоторый контекст кода в поддержку моего плохого объяснения проблемы, которую я надеюсь решить.

В обработчике нажатия кнопки на форме у меня есть это:

    private void button2_Click(object sender, EventArgs e)
    {
        AutoResetEvent autoResetEvent = new AutoResetEvent(false);

        new Thread(delegate() 
        {
            // do something that takes a while.
            Thread.Sleep(1000);

            // Update UI w/BeginInvoke
            this.BeginInvoke(new ThreadStart(
                delegate() { 
                    this.Text = "Working... 1";
                    this.Refresh();
                    Thread.Sleep(1000); // gimme a chance to see the new text
                }));

            // do something else that takes a while.
            Thread.Sleep(1000);

            // Update UI w/Invoke
            this.Invoke(new ThreadStart(
                delegate() {
                    this.Text = "Working... 2";
                    this.Refresh();
                    Thread.Sleep(1000); // gimme a chance to see the new text
                }));

            // do something else that takes a while.
            Thread.Sleep(1000);

            autoResetEvent.Set();
        }).Start();


        // I want the UI to update during this 4 seconds, even though I'm 
        // blocking the mainthread
        if (autoResetEvent.WaitOne(4000, false))
        {
            this.Text = "Event Signalled";
        }
        else
        {
            this.Text = "Event Wait Timeout";
        }
        Thread.Sleep(1000); // gimme a chance to see the new text
        this.Refresh();
    }

Если я не установил тайм-аут в WaitOne (), приложение заблокировалось бы при вызове Invoke ().


Что касается того, почему я хочу сделать это, мне было поручено переместить одну подсистему приложения для выполнения работы в фоновом потоке, но при этом она блокировала рабочий процесс пользователя (основной поток) только иногда и определенные виды работ, связанные только с этой подсистемой.

Ответы [ 10 ]

2 голосов
/ 12 декабря 2008

Обычными действиями, которые блокируют основной поток, являются такие, как открытие окон сообщений или модальное диалоговое окно. Основной код блокируется при вызове MessageBox или ShowDialog.

То, как работают эти элементы (а MessageBox - это просто специализированный модальный диалог), заключается в том, что они содержат собственный насос сообщений, пока они блокируются.

Несмотря на то, что это неприятный хак, вы можете сделать что-то подобное в своем приложении, зацикливая вызов Application.DoEvents (), чтобы поддерживать прокачку пользовательских сообщений, пока вы ожидаете завершения другой задачи. Вы должны быть осторожны, потому что всевозможные неприятные вещи могут привести к тому, что такие сообщения перекачиваются - например, кто-то закрывает форму или повторно вводит ваш текущий обработчик сообщений - модальные диалоги избегают этого, эффективно отключая ввод из формы, которая их запускает.

Я действительно хотел сказать, что BackgroundWorker - лучшее решение, если вы можете сделать его подходящим. Иногда я комбинирую его с модальным «диалоговым окном прогресса», чтобы дать мне фоновую нить / прокачку сообщений и блокировку потока пользовательского интерфейса.

Изменить - расширить последний бит:

Один из подходов, которые я использовал, - это наличие класса «форма выполнения», который принимает объект BackgroundWorker в качестве параметра конструктора и содержит обработчики для событий прогресса и завершения фонового работника, который ему передается.

Форма, которая хочет, чтобы работа была выполнена, создает фонового работника и подключает событие 'работа' (не может вспомнить, как оно называется прямо сейчас), а затем создает диалоговое окно прогресса, в которое оно передает фонового работника. Затем он модально отображает диалоговое окно прогресса, что означает, что он будет ждать (но качает сообщения), пока диалоговое окно прогресса не закроется.

Форма выполнения отвечает за запуск BackgroundWorker из его переопределения OnLoad и закрывает себя, когда видит, что BackgroundWorker завершен. Очевидно, что вы можете добавить текст сообщения, индикаторы выполнения, кнопки отмены, что угодно в форму выполнения.

2 голосов
/ 12 декабря 2008

Это проще, чем вы думаете.

Предложение: когда вам нужен поток для выполнения какой-то случайной работы, получите его из пула потоков, чтобы вам не понадобился странный / подверженный ошибкам код переработки.

Если вы хотите, чтобы что-то в другом потоке обновляло ваш пользовательский интерфейс, вам просто нужна ссылка на форму и вызов Form.Invoke, передавая код пользовательского интерфейса, который вы хотите, чтобы основной поток выполнил; В случае успеха лучше всего выпустить поток пользовательского интерфейса как можно скорее.

Т.е.:

private void button1_Click(object sender, EventArgs e)
{
    // this is the UI thread

    ThreadPool.QueueUserWorkItem(delegate(object state)
    {
        // this is the background thread
        // get the job done
        Thread.Sleep(5000);
        int result = 2 + 2;

        // next call is to the Invoke method of the form
        this.Invoke(new Action<int>(delegate(int res)
        {
            // this is the UI thread
            // update it!
            label1.Text = res.ToString();
        }), result);
    });
}

Надеюсь, это поможет вам:)

РЕДАКТИРОВАТЬ: я сожалею, я не читал часть "блокирование рабочего процесса пользователя".

WindowsForms не предназначены для этого, блокировка основного потока - ПЛОХАЯ (он обрабатывает сообщения из ОС).

Вам не нужно блокировать рабочий процесс пользователя путем замораживания формы (которая в этом случае будет рассматриваться как «не отвечающая» для окон), способ заблокировать рабочий процесс пользователя - отключить любой элемент управления, который вы хотите (с помощью метода Invoke, описанного выше если из другой ветки) даже вся форма !!

2 голосов
/ 11 декабря 2008

Вы хотите использовать класс " BackgroundWorker ", который избавит вас от большей части этой боли ... но, как упоминалось ранее, вы также захотите структурировать его так, чтобы основной поток обновляет пользовательский интерфейс, а работник выполняет тяжелую работу.

1 голос
/ 11 декабря 2008

структурировать ваше приложение так, чтобы основной поток выполнял только обновления пользовательского интерфейса, а вся остальная работа выполнялась во вторичных потоках через рабочую очередь; затем добавьте флаг ожидания для Godot в ваш основной поток и используйте его для защиты метода, добавляющего элементы в рабочую очередь

из любопытства: зачем ты это делаешь?

0 голосов
/ 20 февраля 2009

Я отправил что-то, чего еще не видел, и использую MessageQueues.

  • MainThread блокируется при ожидании следующего сообщения в очереди.
  • Фоновая тема публикует различные типы сообщений в MessageQueue.
  • Некоторые типы сообщений сигнализируют MainThread для обновления элементов пользовательского интерфейса.
  • Конечно, есть сообщение, которое говорит MainThread прекратить блокировку и ожидание сообщений.

Кажется чрезмерным, учитывая, что цикл сообщений Windows где-то уже существует, но он работает.

0 голосов
/ 15 января 2009

Лучше всего посылать работу, но если нужно, может быть что-то вроде этого. Просто вызовите этот метод, чтобы дождаться сигнала, а не вызывать waitone.

private static TimeSpan InfiniteTimeout = TimeSpan.FromMilliseconds(-1); 
private const Int32 MAX_WAIT = 100; 

public static bool Wait(WaitHandle handle, TimeSpan timeout) 
{ 
    Int32 expireTicks; 
    bool signaled; 
    Int32 waitTime; 
    bool exitLoop; 

    // guard the inputs 
    if (handle == null) { 
        throw new ArgumentNullException("handle"); 
    } 
    else if ((handle.SafeWaitHandle.IsClosed)) { 
        throw new ArgumentException("closed wait handle", "handle"); 
    } 
    else if ((handle.SafeWaitHandle.IsInvalid)) { 
        throw new ArgumentException("invalid wait handle", "handle"); 
    } 
    else if ((timeout < InfiniteTimeout)) { 
        throw new ArgumentException("invalid timeout <-1", "timeout"); 
    } 

    // wait for the signal 
    expireTicks = (int)Environment.TickCount + timeout.TotalMilliseconds; 
    do { 
        if (timeout.Equals(InfiniteTimeout)) { 
            waitTime = MAX_WAIT; 
        } 
        else { 
            waitTime = (expireTicks - Environment.TickCount); 
            if (waitTime <= 0) { 
                exitLoop = true; 
                waitTime = 0; 
            } 
            else if (waitTime > MAX_WAIT) { 
                waitTime = MAX_WAIT; 
            } 
        } 

        if ((handle.SafeWaitHandle.IsClosed)) { 
            exitLoop = true; 
        } 
        else if (handle.WaitOne(waitTime, false)) { 
            exitLoop = true; 
            signaled = true; 
        } 
        else { 
            if (Application.MessageLoop) { 
                Application.DoEvents(); 
            } 
            else { 
                Thread.Sleep(1); 
            } 
        } 
    } 
    while (!exitLoop); 

    return signaled;
}
0 голосов
/ 12 декабря 2008

Просто фрагмент кода: не так много времени извините:)

    private void StartMyDoSomethingThread() {
        Thread d = new Thread(new ThreadStart(DoSomething));
        d.Start();
    }

    private void DoSomething() {
        Thread.Sleep(1000);
        ReportBack("I'm still working");
        Thread.Sleep(1000);
        ReportBack("I'm done");
    }

    private void ReportBack(string p) {
        if (this.InvokeRequired) {
            this.Invoke(new Action<string>(ReportBack), new object[] { p });
            return;
        }
        this.Text = p;
    }
0 голосов
/ 12 декабря 2008

Если бы вы могли настроить свой код так, чтобы вы устанавливали флаг после начала процесса, а затем проверяли это в пользовательском интерфейсе, прежде чем начинать дополнительную операцию, я думаю, вам было бы намного легче кодировать это. Я бы создал делегата, который мог бы вызываться из потока в пуле потоков или потока, созданного пользователем, для обновления информации о прогрессе в пользовательском интерфейсе. После того, как фоновый процесс завершен, установите флажок, и теперь нормальные операции пользовательского интерфейса могут продолжаться. Единственное предостережение, о котором вы должны знать, это то, что когда вы обновляете компоненты пользовательского интерфейса, вы должны делать это в потоке, в котором они были созданы, в основном / пользовательском потоке. Для этого вы можете вызвать метод Invoke () для любого элемента управления, который находится в этом потоке, и передать ему делегат и параметры, необходимые для его вызова.

Вот ссылка на учебник, который я написал некоторое время назад о том, как использовать Control.Invoke ():

http://xsdev.net/tutorials/pop3fetcher/

0 голосов
/ 12 декабря 2008

Я согласен с другими, которые предлагают вам использовать Background Worker. Это делает тяжелую работу и позволяет интерфейсу продолжить. Вы можете использовать Report Progress of Background Worker, чтобы инициировать время, когда основная форма может быть отключена, когда она выполняет действия в фоновом режиме, и затем снова включить, когда «определенные экземпляры» завершили обработку.

Дайте мне знать, если это поможет! JFV

0 голосов
/ 12 декабря 2008

Вы, вероятно, должны реструктурировать свой код, как предлагали другие, но в зависимости от поведения, которое вы ищете, вы также можете захотеть взглянуть на использование Thread.Join в фоновом рабочем потоке. Объединение фактически позволяет вызывающему потоку обрабатывать события COM и SendMessage, пока он ожидает завершения другого потока. Похоже, что это может быть опасно в некоторых случаях, но на самом деле у меня была пара сценариев, когда это был единственный способ дождаться, пока другой поток завершит работу чисто.

Тема .. ::. Метод соединения

Блокирует вызывающий поток до поток завершается, продолжая выполнить стандартные COM и SendMessage насосное.

(из http://msdn.microsoft.com/en-us/library/95hbf2ta.aspx)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...