C #: заполнение пользовательского интерфейса с использованием отдельных потоков

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

Из того, что я читал здесь (ссылка) обновление элементов пользовательского интерфейса из любого потока, кроме потока пользовательского интерфейса, не должно выполняться, и все же это работает?

Thread t0 = new Thread(new ThreadStart(PopulateListView1));
t0.IsBackground = true;
t0.Start();

Thread t1 = new Thread(new ThreadStart(PopulateListView2));
t1.Start();

Thread t2 = new Thread(new ThreadStart(PopulateListView3));
t2.Start();

Thread t3 = new Thread(new ThreadStart(PopulateListView4));
t3.Start();

Сама ошибка - это System.InvalidOperationException «Изображение не может быть добавлено в ImageList». что заставляет меня задуматься, связан ли приведенный выше код каким-либо образом.

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

Обновлять:

Возможно, я дал некоторую дезинформацию, сославшись на «форму». Приложение представляет собой приложение Windows Forms, но код взят из подключаемого приложения, основанного на пользовательском элементе управления. Потоки создаются внутри метода инициализации, публично предоставляемого этим элементом управления. Списки и т. Д. Также являются частью этого плагина usercontrol.


person Andrew    schedule 10.05.2010    source источник
comment
Это элементы управления пользовательского интерфейса этого плагина, которые вы хотите заполнить, или информацию из этого элемента управления пользовательского интерфейса, которую вы хотите заполнить в другом из ваших?   -  person Will Marcouiller    schedule 10.05.2010
comment
Usercontrol больше похож на отдельное приложение-плагин, он содержит несколько списков, и именно они обновляются.   -  person Andrew    schedule 10.05.2010
comment
Я понимаю. У вас есть исходный код этого плагина UserControl?   -  person Will Marcouiller    schedule 10.05.2010
comment
Да, и хост-контейнер, и пользовательское управление принадлежат нам, но ответственный за них разработчик больше не работает в компании. Метод инициализации в пользовательском элементе управления вызывается контейнером, и именно этот метод создает потоки, заполняющие элементы управления списком.   -  person Andrew    schedule 10.05.2010
comment
Затем я бы порекомендовал, возможно, попытаться найти способ вызвать поток пользовательского интерфейса, чтобы он мог заполнить элементы управления, что-то с делегатом или любым обходным путем, как я предлагал. Мой ответ вам понятен?   -  person Will Marcouiller    schedule 10.05.2010
comment
В случае сценария плагина, который вы описываете, обновление кода с помощью BeginInvoke может помочь. Думаю, это то же самое, что говорит @Will Marcouiller.   -  person hitec    schedule 11.05.2010
comment
@hitec: Спасибо за ваше наблюдение! @ Андрей: Я рад видеть, что помог!   -  person Will Marcouiller    schedule 11.05.2010


Ответы (5)


Внутри ваших методов потоков, таких как DoWork () для BackgroundWorker class, например, вам нужно будет создать метод делегата для заполнения элемента управления пользовательского интерфейса. Затем, проверяя, требуется ли вызывать элемент управления пользовательского интерфейса (InvokeRequired), а затем вызвать его, когда это потребуется.

private delegate IList<MyObject> PopulateUiControl();

private void myThread_DoWork(object sender, DoWorkEventArgs e) {
    PopulateUiControl myDelegate = FillUiControl;

    while(uiControl.InvokeRequired)
        uiControl.Invoke(myDelegate);
}

private IList<MyObject> FillUiControl() {
    uiControl.Items = myThreadResultsITems;
}

Это не точный рабочий код, так как я не могу тратить время на исследования и т. Д., Но это поможет вам добиться успеха.

В конце концов, я согласен с другими, стараюсь избегать таких вещей в будущем, так как может быть сложно отладить или выявить какое-то странное поведение. Возможно, в .NET 4 есть некоторые улучшения по этой теме, поскольку Microsoft упорно работала над упрощением параллелизма для использования многоядерных процессоров для разработчиков.

person Will Marcouiller    schedule 10.05.2010

  • НЕ ИСПОЛЬЗУЙТЕ для этого потоки - если вам нужно сделать это асинхронно, используйте WOrkItems в THreadPool. Использование потоков в целом должно быть сброшено для долго работающих элементов - для этого лучше подходят THreadPool или новый API задач .NET 4.0.

  • Элементы пользовательского интерфейса следует использовать только в потоке создания элементов. Он «работает» или нет, в зависимости от того, какую версию .NET Framework вы используете или что это за элемент управления на самом деле, если вы нарушите его.

person TomTom    schedule 10.05.2010
comment
Поскольку я писал примерно то же самое, я сдался. Вот ссылка об этом: yoda.arachsys.com/csharp/threads/winforms .shtml - person Timores; 10.05.2010

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

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

PS: Я предполагаю, что когда вы говорите UI, вы имеете в виду WindowsForms, а не WPF. Я успешно использовал вышеуказанное решение в WinForms.

Обновление: вот несколько статей, в которых подробно объясняется концепция: * Threading в Windows Forms * вызывает поток пользовательского интерфейса WinForms: подробный обзор вызова / BeginInvoke / InvokeRequred

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

person hitec    schedule 10.05.2010
comment
Я действительно имею в виду формы Windows, в частности пользовательский элемент управления в форме Windows. Я должен был изначально включить эту информацию, а теперь обновил вопрос. - person Andrew; 10.05.2010

Никогда не обновляйте пользовательский интерфейс из рабочего потока. Программа может иногда работать, но это неопределенное поведение. Добавьте эту строку в код инициализации программы:

Control.CheckForIllegalCrossThreadCalls = true;

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

person Alex F    schedule 10.05.2010

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

person Roopesh Shenoy    schedule 10.05.2010