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

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

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

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

    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 ().


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

11.12.2008 20:37:07
10 ОТВЕТОВ

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

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

1
11.12.2008 20:41:01
Что касается изменения структуры приложения, как вы предложили, это не совсем мое приложение; и этот корабль отплыл.
CrashCodes 11.12.2008 20:46:39
Что касается того, почему я хотел бы сделать это, мне было поручено переместить одну подсистему приложения для выполнения работы в фоновом потоке, но при этом она блокировала рабочий процесс пользователя (основной поток) только иногда и для определенных типов. Работа.
CrashCodes 11.12.2008 20:49:41
@CrashCodes: слишком расплывчато, извините; «иногда» и «определенные типы» могут означать что угодно - как вы собираетесь контролировать, когда и что?
Steven A. Lowe 11.12.2008 20:52:41
«Иногда» и «определенные типы» не имеют значения. Их будут контролировать простые операторы if, чтобы определить, ждать или сколько ждать. Дело в том, что мне нужно будет обновить пользовательский интерфейс, блокируя прохождение пользователя через приложение.
CrashCodes 11.12.2008 20:57:32
@CrashCodes: если это все, что вы хотите, периодически вызывайте Application.DoEvents () во время ожидания
Steven A. Lowe 11.12.2008 22:18:15

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

2
11.12.2008 20:43:53

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

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

Когда вы хотите, чтобы что-то в другом потоке обновило ваш пользовательский интерфейс, вам просто нужна ссылка на форму и вызов 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.12.2008 21:18:32

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

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

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

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

Редактировать - расширить на последний бит:

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

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

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

2
11.12.2008 22:02:05
Я не думаю, что хочу DoEvents () именно по тем причинам, которые вы перечислили. Можете ли вы рассказать о фоновом рабочем с модальным диалогом прогресса? Итак, я могу получить модальную форму, обновить ее из потока bg и даже сделать так, чтобы поток bg убил ее?
CrashCodes 11.12.2008 21:23:09
@Будет. Пришел сюда, чтобы опубликовать именно то, что делает ваш диалог ... Я думаю, что это лучшее решение.
Nicholas Mancuso 15.01.2009 19:01:13

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

Тема .. ::. Присоединиться

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

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

0
11.12.2008 21:18:52

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

Позвольте мне знать, если это помогает! JFV

0
11.12.2008 21:24:42

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

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

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

0
11.12.2008 21:46:41

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

    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.12.2008 08:56:31

Лучше всего посылать работу, но если нужно, может быть что-то вроде этого. Просто вызовите этот метод, чтобы дождаться сигнала, а не вызывать 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
15.01.2009 18:45:50
Application.MessageLoop не поддерживается CF
CrashCodes 12.02.2009 20:41:06
DoEvents позволит пользователю нажимать кнопки и т. Д. Я просто хочу, чтобы экран мог обновляться.
CrashCodes 12.02.2009 20:43:18
РЕШЕНИЕ

Я пошел с кое-чем, что я еще не видел опубликованным, который должен использовать MessageQueues.

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

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

0
20.02.2009 16:31:07