Использование потоков и .Invoke() и элементы управления по-прежнему остаются неактивными - C#

Я пытаюсь заполнить текстовое поле некоторыми данными, а именно именами нескольких инструментов построчно.

У меня есть класс, который будет генерировать и возвращать список инструментов, затем я перебираю список и добавляю новую строку в текстовое поле после каждой итерации.

Начало темы:

private void buttonListInstruments_Click(object sender, EventArgs e)
        {
            if (ins == null)
            {
                ins = new Thread(GetListOfInstruments);
                ins.Start();
            }
            else if (ins != null)
            {
                textBoxLog.AppendText("Instruments still updating..");
            }

        }

Делегировать для обновления текстового поля:

public delegate void UpdateLogWithInstrumentsCallback(List<Instrument> instruments);

private void UpdateInstruments(List<Instrument> instruments)
        {
            textBoxLog.AppendText("Listing available Instruments...\n");

            foreach (var value in instruments)
            {
                textBoxLog.AppendText(value.ToString() + "\n");
            }
            textBoxLog.AppendText("End of list. \n");

            ins = null;
        }

Вызов элемента управления:

private void GetListOfInstruments()
        {
            textBoxLog.Invoke(new UpdateLogWithInstrumentsCallback(this.UpdateInstruments),
                new object[] { midiInstance.GetInstruments() });
        }

Примечание. GetInstruments() возвращает список типа инструмент.

Я внедряю therads, чтобы попытаться сохранить работоспособность графического интерфейса, пока обновляется текстовое поле. По какой-то причине другие элементы управления пользовательского интерфейса в WinForm, такие как отдельное поле со списком, остаются неактивными при нажатии до тех пор, пока текстовое поле не завершит обновление.

Правильно ли я использую потоки?

Спасибо.


person Jamie Keeling    schedule 06.02.2011    source источник
comment
Я бы рекомендовал использовать SynchronizationContext -- IIRC, если вы находитесь в том же потоке (UI), вы должны использовать BeginInvoke (не Invoke) для фактической публикации в очереди сообщений пользовательского интерфейса. Сообщение SynchronizationContext автоматически позаботится об этом (как только вы получите правильный SC для пользовательского интерфейса WinForm :-)   -  person    schedule 07.02.2011


Ответы (6)


Вы ничего не сделали, метод UpdateInstruments() по-прежнему работает в потоке пользовательского интерфейса, как и раньше. Не совсем уверен, почему вы видите такую ​​большую задержку, это должно быть большое количество инструментов. Возможно, вы можете сделать его менее медленным, сначала добавив их все в StringBuilder, а затем добавив его значение ToString() в TextBox. Это исключает довольно дорогой вызов Windows.

person Hans Passant    schedule 06.02.2011
comment
midiInstruments.GetInstruments() работает в рабочем потоке, я предполагаю, что это интенсивная задача. - person Ben Voigt; 07.02.2011
comment
Необходимо ли тогда использование .Invoke? Я прочитал несколько статей в MSDN и понял, что это путь вперед. - person Jamie Keeling; 07.02.2011
comment
Это просто заблокирует рабочий поток, нет смысла его блокировать. Здесь нет эффективной разницы между BeginInvoke и Invoke. Сколько строк добавляется? - person Hans Passant; 07.02.2011
comment
Кстати, если midiInstance является COM-объектом, то GetInstruments() также может работать в потоке пользовательского интерфейса. Многие COM-серверы не являются потокобезопасными, COM автоматически помогает им, направляя вызовы в поток, в котором они были созданы. - person Hans Passant; 07.02.2011
comment
Примерно 127 строк длиной не более двадцати символов. - person Jamie Keeling; 07.02.2011
comment
Этого недостаточно, чтобы объяснить длительную задержку. Это должно быть вызвано COM. Попробуйте создать экземпляр midiInstruments в рабочем потоке. Это может работать неправильно, но вы быстро обнаружите это. - person Hans Passant; 07.02.2011
comment
Если я создам экземпляр в рабочем потоке, мне придется изменить значительную часть существующего кода, поскольку он используется многими другими методами. Все, что я пытаюсь сделать, это обновить текстовое поле, сохраняя при этом отзывчивость остальной части пользовательского интерфейса. Я не схожу с ума, думая, что это можно сделать правильно? знак равно - person Jamie Keeling; 07.02.2011
comment
Что ж, могу ли я использовать фрагмент кода, небезопасный для потока, в потоке, к сожалению, вопрос без хорошего ответа. - person Hans Passant; 07.02.2011
comment
Хорошо, я попытаюсь найти способ повторно реализовать то, что мне нужно, потокобезопасным способом, спасибо! - person Jamie Keeling; 07.02.2011

Я бы рекомендовал использовать SynchronizationContext в целом:

Из потока пользовательского интерфейса, например. инициализация:

// make sure a SC is created automatically
Forms.WindowsFormsSynchronizationContext.AutoInstall = true;
// a control needs to exist prior to getting the SC for WinForms
// (any control will do)
var syncControl = new Forms.Control();
syncControl.CreateControl();
SyncrhonizationContext winformsContext = System.Threading.SynchronizationContext.Current;

Далее, из любой ветки, желающей написать в указанный выше СЦ:

// later on -- no need to worry about Invoke/BeginInvoke! Whoo!
// Post will run async and will guarantee a post to the UI message queue
// that is, this returns immediately
// it is OKAY to call this from the UI thread or a non-UI thread
winformsContext.Post(((state) => ..., someState);

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

void UpdateStuff (List<string> _stuff) {
    var stuff = new Queue<string>(_stuff); // make copy
    SendOrPostCallback fn = null; // silly so we can access in closure
    fn = (_state) => {
        // this is in UI thread
        Stopwatch s = new Stopwatch();
        s.Start();
        while (s.ElapsedMilliseconds < 20 && stuff.Count > 0) {
          var item = stuff.Dequeue();
          // do stuff with item
        }
        if (stuff.Count > 0) {
          // have more stuff. we may have run out of our "time-slice"
          winformsContext.Post(fn, null);
        }
    };
    winformsContext.Post(fn, null);
}

Удачного кодирования.

person Community    schedule 06.02.2011

Измените эту строку:

textBoxLog.Invoke(new UpdateLogWithInstrumentsCallback(this.UpdateInstruments),
                new object[] { midiInstance.GetInstruments() });

с этим:

textBoxLog.BeginInvoke(new UpdateLogWithInstrumentsCallback(this.UpdateInstruments),
                new object[] { midiInstance.GetInstruments() });
person Dimitris Tavlikos    schedule 06.02.2011
comment
Почему изменение его на асинхронный метод поможет? - person Jamie Keeling; 07.02.2011
comment
Потому что именно метод Invoke блокирует основной поток. Вызов BeginInvoke не блокирует основной поток. - person Dimitris Tavlikos; 07.02.2011
comment
Нет, основной поток не заблокирован. Вспомогательный поток блокируется, пока текстовое поле обновляется, но на самом деле это не проблема. - person Ben Voigt; 07.02.2011

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

person Ondrej Tucny    schedule 06.02.2011
comment
@Ben Voigt Я предполагаю, что отдельный поток загрузки данных существует не просто так. Подача данных один за другим (один = элемент или фрагмент), следовательно, является основной идеей для получения некоторой асинхронности. - person Ondrej Tucny; 07.02.2011

нет, вы запускаете поток, а затем используете вызов, что в основном означает, что вы возвращаетесь к потоку пользовательского интерфейса, чтобы выполнить работу... так что ваш поток ничего не делает!

person Keith Nicholas    schedule 06.02.2011
comment
midiInstruments.GetInstruments() работает в рабочем потоке, я предполагаю, что это интенсивная задача - person Ben Voigt; 07.02.2011
comment
@Ben Да, задача, по сути, обращается к API и возвращает список доступных инструментов, я стремлюсь обновить текстовое поле с помощью этих инструментов, позволяя использовать остальную часть графического интерфейса. - person Jamie Keeling; 07.02.2011

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

person Ben Voigt    schedule 06.02.2011