Как заблокировать поток пользовательского интерфейса из другого потока или заставить форму работать в потоке пользовательского интерфейса - PullRequest
2 голосов
/ 16 декабря 2008

Требование для моего приложения: если оно теряет соединение с базой данных, то должно появиться большое модальное диалоговое окно «Нет соединения. Попробуйте позже», блокирующее все взаимодействия с пользователем до тех пор, пока соединение не будет восстановлено.

Я достигаю этого путем запуска приложения, запускающего экземпляр класса DeviceMonitor. Этот класс создает System.Threading.Timer и каждый тик (обычно 1 секунду), а также несколько других вещей, которые он пытается извлечь из базы данных. Если отрисовка не удалась и причина была определена из-за отсутствия соединения, исключение обрабатывается путем всплывающего окна вышеупомянутого диалога. Аналогичным образом, если выборка данных завершается успешно и диалоговое окно в данный момент работает, то оно принудительно закрывается.

Проблема в том, что, хотя все это работает нормально, диалоговое окно ConnectionLost не блокирует взаимодействие пользователя с пользовательским интерфейсом. Это имеет смысл, поскольку событие Timer.Elapsed вызывается в его собственном потоке, а noConnectionDialog.ShowDialog () вызывается из функции обратного вызова, блокируя поток, в котором он находится, но не поток пользовательского интерфейса.

Насколько я понимаю, мне нужно либо принудительно запустить noConnectionDialog.ShowDialog () в потоке пользовательского интерфейса, либо заблокировать поток пользовательского интерфейса до вызова noConnectionDialog.Hide (), но я не знаю, как это сделать.

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

РЕДАКТИРОВАТЬ: Дополнительная информация - это стилизованный диалог, а не просто окно сообщения. Он создается, когда мое приложение запускается Castle Windsor и внедряется в класс DialogFactory, который передается. Таким образом, к диалогу обращается

var d = _dialogFactory.GetNoConnectionDialog();
d.ShowDialog();

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

Ответы [ 3 ]

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

Я почти уверен, что предложенное Марком должно сработать. Вот как я бы написал, чтобы использовать ваш диалог вместо MessageBox:

someControl.Invoke((Action)delegate {
    var d = _dialogFactory.GetNoConnectionDialog();
    d.ShowDialog();
}, null);

Если это действительно не сработало, я имел успех в прошлом, используя элемент управления Timer (System.Windows.Forms.Timer) в моей форме и очередь действий с функцией Tick, которая выглядит следующим образом:

void timer_Tick(object sender, System.EventArgs e)
{
    lock(queue)
    {
        while(queue.Count > 0)
        {
            Action a = queue.Dequeue();
            a();
        }
    }
}

И когда ваш класс DeviceMonitor должен показать пользовательский интерфейс, он сделает это:

lock(queue)
{
    queue.Enqueue((Action)delegate 
    {
        var d = _dialogFactory.GetNoConnectionDialog();
        d.ShowDialog();
    });
}

При этом я действительно хочу повторить, что я думаю, что метод Марка должен работать правильно, и я бы использовал мой метод Timer + queue, только если вы абсолютно уверены, что Control.Invoke не будет работать для вас.

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

Если у вас есть доступ к элементу пользовательского интерфейса, вы можете перейти к потоку пользовательского интерфейса, используя такие вещи, как:

someControl.Invoke((Action)delegate {
    MessageBox.Show(someControl, "Foo");
    // could also show a form here, etc
});

(someControl в MessageBox. Show помогает родительскому окну сообщения)

Если у вас нет доступа к элементу управления пользовательского интерфейса, вы также можете использовать sync-context:

SynchronizationContext.Current.Post(delegate {
    MessageBox.Show("Foo");
}, null);

Но легче удерживать элемент управления; -p

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

Это называется маршалингом и является очень простой концепцией, когда вы читаете хороший материал (Google - ваш друг).

Если ваш фоновый поток имеет делегата, который вызывает объект, принадлежащий потоку пользовательского интерфейса, то этот метод (на вызываемом конце делегата) просто должен маршалировать себя обратно в поток, которому принадлежит его объект (который будет поток пользовательского интерфейса), а затем заблокировать. Это очень простой код (IsInvokeRequired), вам просто нужно понять, как все выложить. (Это в основном повторяет то, что сказал Марк, но с более высокого уровня.)

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