Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Разработка Windows Forms > Многопоточное приложение


Автор: SaS1 30.8.2007, 01:06
Пишу такой код:

Код


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace Threads
{
    
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void RunSecondThread()
        {
            for (int count = 0; count <= 20; count++)
            {
                listBox1.Items.Add(count);
                Thread.Sleep(50);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread thrd = new Thread(new ThreadStart(this.RunSecondThread));
            thrd.Start();

        }

       

        private void radioButton1_CheckedChanged(object sender, EventArgs e)
        {
            textBox1.Text = radioButton1.Text;
        }

        private void radioButton2_CheckedChanged(object sender, EventArgs e)
        {
            textBox1.Text = radioButton2.Text;
        }

        private void radioButton3_CheckedChanged(object sender, EventArgs e)
        {
            textBox1.Text = radioButton3.Text;
        }
    }


    
}




ОН компилируется, но во время выполнения выдаёт такую ошибку:

Cross-thread operation not valid: Control 'listBox1' accessed from a thread other than the thread it was created on.

на строке listBox1.Items.Add(count);

И я вот никак не пойму как сделать это правильно.
Помогите пожалуйста.

Автор: Mr_Smith 30.8.2007, 04:39
Аксиома многопоточных приложений на .НЕТ, что "контролы можно менять только в контексте породившего их потока" поэтому компилятор на тебя и ругается

Автор: SpaceSpace 30.8.2007, 07:30
Это можно обойти, если делать Invoke делегата функции которая изменяет ресурс в другом потоке

во превых,
поместим в отдельный метод  , который обращается к ресурсу

Код

 public void AsyncChange()

if (this.InvokeRequired)
{
ThreadStart updateStatus = new  ThreadStart(AsyncChange);
this.Invoke(updateStatus)
}
else
{
      // тут изменяеш  ресурс в другого потока

            for (int count = 0; count <= 20; count++)
            {
                listBox1.Items.Add(count);
                Thread.Sleep(50);
            }
}
}

Код

       private void button1_Click(object sender, EventArgs e)
        {
            Thread thrd = new Thread(new ThreadStart(AsyncChange()));
            thrd.Start();

        }


 

Автор: mihryak 30.8.2007, 11:47
с делегатами посимпатичнее выглядит
Код

        private void button1_Click(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new EventHandler(button1_Click), sender, e);
            }
            else
            {
                ...
            }
        }

или (для произвольных сигнатур)
Код

        private delegate void MyHandler(object sender, EventArgs e);
        private void button1_Click(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new MyHandler(button1_Click), sender, e);
            }
            else
            {
                ...
            }
        }

Автор: SpaceSpace 31.8.2007, 07:35
mihryak
молодец, повторил то же самое что и я!
продолжай в том же духе.

Код

ThreadStart updateStatus = new  ThreadStart(AsyncChange);


ThreadStart  - это и есть делегать на запуск непараметризированного потока

Автор: mihryak 31.8.2007, 09:13
SpaceSpace,  обрати внимание, что в первом случае не потребовалось писать дополнительный метод, т.к. делегат EventHandler<EventArgs> определён в MSDN, более того - в твоём коде теряется возможность не только обработать в большинстве случаев ненужные аргументы EventArgs, но и куда более полезный sender. А ведь помимо EventHandler<EventArgs> есть и море других "типовых" generic-обработчиков, где аргументы могуть быть более полезными.
Во втором случае вместо дополнительного метода потребовалось только объявить делегат с требуемой сигнатурой, и опять-таки - параметры не теряются, кода меньше, и он выглядит немного понятнее.
Да, кроме ThreadStart, есть и ParametrizedThreadStart, но и читаемостью кода та же штука. А сочетание компактности и читаемости - основные признаки хорошего кода, можно ведь и через emit код набросать, но кому это нужно? smile
ПС. больше оффтопить не буду, ответы автору даны.

Автор: SpaceSpace 31.8.2007, 09:26
mihryak
Я не считаю это оффтопом.
просто объясни 
Цитата

Во втором случае вместо дополнительного метода потребовалось


где содержится зерно логики.
Да, я согласен, что ты записал код без дополнительного метода,
согласись, что модернизировать мое предложение не составит труда - и оно также будет вызываться из
обработчика клика по кнопке.
Ты считаешь, что это логически и архитектурно обосновано?
Как мне кажется,
абсолютно нелогично делать
BeginInvoke(new EventHandler(button1_Click), sender, e);
т.к. это нарушает стройность и логику пиложения,
ведь после запуска нужного нам метода может потребоваться и другая логика,
именно поэтому Invoke был вынесен за пределы button1_Click,
это обосновано и оправдано , жду ваших корректив по этому утверждению

На счет  того что бывают разнве делегаты и свои и дженереки - с этим никто не спорил,
согласен, что для примера логичнее, круче и моднее использовать EventHandler<EventArgs>,

Автор: mihryak 31.8.2007, 09:54
Ок, давай тогда возьмём более реальный пример. Не думаю, что у кого-либо в здравом уме не для тестовых целей возникнет необходимость инвочить обработчик на нажатие кнопки (мышью в другом потоке не кликнешь, а вызывать этот обработчик из другого потока и есть то самое упомянутое нарушение стройности и глупая привязка логики к пользовательскому интерфейсу).
Также отбросим вариант, когда потоку просто нужно оповещать форму, что что-то где-то, так что действуй (опять-таки - либо тестовый набросок, либо трэд изменял не-UI свойства формы, и ей нужно только сказать, что эти свойства уже изменились и нужно перестроиться, а это тоже нелучшее решение).
Итак, остаётся один из главных вариантов - трэд через эвенты (например) посылает сообщения, содержащие какие-то нужные для бизнес-лигики, а потом и UI данные. В этом случае придётся использовать параметризированный ThreadStart, а у него простое объявление делегата однозначно выигрывает.
Таким образом, в обработчике эвента от треда нужно будет обрабатываться только эвент от треда, что к UI не имеет никакого отношения, и
Цитата(SpaceSpace @  31.8.2007,  10:26 Найти цитируемый пост)
и оно также будет вызываться из
обработчика клика по кнопке.

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

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

Автор: SpaceSpace 31.8.2007, 10:13
Цитата

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

Вот ты и признал, что твой пример ничем не лучше моего.
именно на это я и указывал.  smile 

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

Цитата

Таким образом, в обработчике эвента от треда нужно будет обрабатываться только эвент от треда, что к UI не имеет никакого отношения

жжешь  smile 

Не надо мне предьявлять того, на истинность чего я не посягал.

Надо уметь признавать свои ошибки и аргументировать свою точку зрения

Автор: mihryak 31.8.2007, 10:23
резюме такое - нефиг начинать спорить, взяв за основу надуманные или нежизненные примеры, спор выходит ни о чём smile
ну и напоследок тоже не удержусь от укольчика - в msdn async programming best practice всё-таки при InvoceRequired вместо создания и запуска  тредов используют таки инвоки делегатов :-Р

Автор: SpaceSpace 31.8.2007, 10:26
согласен с тобой.
приятно пообщаться с умным человеком.
 smile 

Автор: SaS1 1.9.2007, 18:21
Ребята, спасибки за ответ. Я попробовала то, что вы предлогали и вроде ошибка не возникала. Но вот какая проблема применительно к моему примеру. Получается что сначала после нажатия бутона прога просто виснет на небольшое время, а потом сразу выводит все циферки и в то время, как она виснет, я не могу по радиобутонам нажимать. 
Простите за надуманный пример. В идеале у меня должно получиться приложение сервер, которое постоянно ждёт сообщений от клиентов, но при этом пользователь может с ним работать. Вот для этого мне и нужны были несколько потоков:(
Так что я до сих пор не понимаю как мне это так хитро сделать. Может поможете?

Автор: 1stein 1.9.2007, 19:33
по-моему вам нужен BackgroundWorker. Вот здесь он на мой взгляд неплохо описан: http://www.rsdn.ru/article/dotnet/WinForms20.xml

Автор: SpaceSpace 3.9.2007, 07:36
SaS1
если у тебя будет сервер, то логичнее использовать пул потоков.
т.к. при создании нового потока затрачиваются большие  резурсы (скажем, 1000 циклов проца),
поэтому у тебя 
Цитата

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

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)