Почему не контролирует обновление / обновление в середине процесса - PullRequest
12 голосов
/ 26 февраля 2010

У меня есть форма Windows (C # .NET) с statusLabel, которую я не могу обновить в середине процесса в методах обработчика событий. Мой код выглядит так ...

    void Process_Completed(object sender, EventArgs e)
    {

        string t = "Process is finished!";
        this.Invoke(new StatusLabelUpdator(updateStatusLabel), new object[] { t });
    }

    void Process_Started(object sender, EventArgs e)
    {
        string t = "Process has begun";
        this.Invoke(new StatusLabelUpdator(updateStatusLabel), new object[] { t });
    }

    private delegate void StatusLabelUpdator(string text);
    private void updateStatusLabel(string text)
    {
        StatusLabel1.Text = text;
        statusStrip1.Invalidate();
        statusStrip1.Refresh();
        statusStrip1.Update();
    }

Когда я запускаю код, после запуска процесса запускается метод Process_Started, а через пару секунд запускается метод Process_Completed. По какой-то причине я не могу получить метку состояния, чтобы когда-либо отображать «Процесс начался». Отображается только «Процесс завершен!». Как вы можете видеть, я пытался сделать недействительной, обновить и обновить строку состояния, которая содержит метку состояния, но безуспешно. Я не могу вызвать update / refresh / invalidate для самой метки статуса, потому что эти методы ему недоступны. Что я делаю не так?

ДОБАВЛЕННАЯ ИНФОРМАЦИЯ:

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

public void DoSomeProcess()
{
    TriggerProcessStarted();
    System.Threading.Thread.Sleep(2000);   // For testing..
    TriggerProcessComplete();
}

и внутри методов TriggerProcessxxxx я запускаю события, используя этот код ...

var EventListeners = EH.GetInvocationList();    //EH is the appropriate EventHandler
if (EventListeners != null)
{
    for (int index = 0; index < EventListeners.Count(); index++)
    {
        var methodToInvoke = (EventHandler)EventListeners[index];
        methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, new object[] { });
    }
}

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

private void updateStatusLabel(string text)
{
    StatusLabel1.Text = text;
    statusStrip1.Refresh();
    Application.DoEvents(); 
}

Так что я предполагаю, что «обработка» происходит в потоке пользовательского интерфейса, но обработчик событий вызывается в его собственном потоке, который затем вызывает обновление элемента управления обратно в потоке пользовательского интерфейса. Это глупый способ делать вещи? Примечание. Класс, содержащий метод DoSomeProcess (), находится в отдельной .NET ClassLibrary, на которую я ссылаюсь.

1 Ответ

17 голосов
/ 26 февраля 2010

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

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

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

Re

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

Я предполагаю, что DoSomeProcess запускается из потока пользовательского интерфейса (например, в прямом ответе на нажатие кнопки или подобное). Если так, то да, ваша обработка определенно находится в потоке пользовательского интерфейса. Поскольку TriggerProcessStarted вызывает ваш обратный вызов в асинхронном режиме через BeginInvoke, вы не знаете, когда он будет выполняться, но в любом случае ваш код сразу же запускается в обработку, никогда не уступая, поэтому никто больше не собирается быть в состоянии схватить эту нить. Поскольку это поток пользовательского интерфейса, вызов делегата будет блокироваться при вызове Invoke, устанавливающем текст метки, после чего он должен ожидать поток пользовательского интерфейса (который занят обработкой). (И это при условии, что это запланировано в другом потоке; я не мог на 100% убедить себя в любом случае, потому что у Microsoft есть два разных BeginInvoke s - что, как признал один из дизайнеров IIRC, было действительно глупой идеей - и это с тех пор как я боролся с этим.)

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

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