Формы и потоки - я не понимаю...
От: .beginner  
Дата: 11.12.12 18:29
Оценка:
Господа,
весь день я блуждал в лабиринтах Invoke, BeginInvoke, EndEnvoke,
а также AsyncOperationManager и пространства имен Tasks.Task.
Но у меня очень простая задача:
я хочу из виндовой формы запустить два потока.
Эти потоки что то делают.
Время от времени они мне в основной UI — поток рапортуют о результатах своей деятельности.

И я до сих пор расшибаю лоб о то, что вызов из параллельного потока приводит к сообщению
"Недопустимая операция в нескольких потоках: попытка доступа к элементу
управления "textBox1" не из того потока, в котором он был создан".
Есть какой то способ из параллельного потока передать данные в основной поток?
У деда мороза этого попросить, что ли...
Re: Формы и потоки - я не понимаю...
От: .beginner  
Дата: 11.12.12 18:47
Оценка:
Я бы хотел конкретизировать свой вопрос.
я сталкиваюсь с тем, что мне нужно из рабочего потока послать в UI — поток сообщение.
Для этого обычно используется метод Post ( что то вроде operation.Post )
Я не понимаю, что за первый аргумент у функции Post ( по смыслу — это callback ) и как его создать.
по примеру http://www.gotdotnet.ru/files/254/ я этого понять не могу.
Если я смогу этот пример заставить заработать — я считаю что для меня задача решена.
Re: Формы и потоки - я не понимаю...
От: QrystaL Украина  
Дата: 11.12.12 19:16
Оценка:
Здравствуйте, .beginner, Вы писали:
B>Есть какой то способ из параллельного потока передать данные в основной поток?


        private void button1_Click(object sender, EventArgs e)
        {
            var t = new Thread(Do);
            t.IsBackground = true;
            t.Start();
        }

        private void Do()
        {
            int i = 0;
            while (true)
            {
                Thread.Sleep(1000);
                i++;
                Invoke(new Action(() => textBox1.Text = i.ToString()));
            }
        }
Re[2]: Ну это ж надо!
От: .beginner  
Дата: 11.12.12 19:20
Оценка:
Все получилось.
Как обычно, через задницу — в смысле, нет нормального объяснения, оттого и путаница.
Ответ содержится тут :
а именно в строчках

// initalized with 
//    onProgressUpdatedDelegate = new SendOrPostCallback(UpdateProgress);


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

Класс, который делает работу:
    public class Worker
    {
        private SendOrPostCallback onProgressUpdatedDelegate;

        public void Init(SendOrPostCallback cb)
        {
            onProgressUpdatedDelegate = cb;
        }
        
        public void DoWork(AsyncOperation operation)
        {
            long maxCnt = 400000000;
            Random rnd = new Random();
            int fc = rnd.Next(1, 10);
            for (int j = 0; j < fc; j++)
            {
                for (long i = 0; i < maxCnt; i++)
                {
                    long s = i*2;
                }

                operation.Post(onProgressUpdatedDelegate, j);

            }
        }
    }


Вызов в UI — потоке этого класса, чтобы он работал, и вообще все, что в UI — потоке:
        public delegate void AsyncOperationInvoker(AsyncOperation operation);
        
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {

            // someData - это данные которые надо передать в рабочий поток (см. метод WorkerThread)
            // в методе WorkerThread данные доступны через operation.UserSuppliedState;
            string someData = DateTime.Now.ToString();
            
            // чтобы из рабочего потока можно было "присоединиться" к текущему потоку.
            AsyncOperation ao = AsyncOperationManager.CreateOperation(someData);

            // запустить рабочий поток
            Worker wrk = new Worker();
            SendOrPostCallback onProgressUpdatedDelegate = new SendOrPostCallback(UpdateProgress);
            wrk.Init(onProgressUpdatedDelegate);

            AsyncOperationInvoker worker1 = new AsyncOperationInvoker( wrk.DoWork );
            worker1.BeginInvoke(ao, null, null);

            textBox1.Text = DateTime.Now.ToLongTimeString() + " запустили wrk.DoWork()" + Environment.NewLine;
            textBox1.Refresh();

        }

        public void UpdateProgress(object obj)
        {
            int stepNumber = (int) obj;
            textBox1.Text += DateTime.Now.ToLongTimeString() + " UpdateProgress вызван! step=" + stepNumber + Environment.NewLine;
        }
Re[2]: Формы и потоки - я не понимаю...
От: .beginner  
Дата: 11.12.12 19:22
Оценка:
Здравствуйте, QrystaL, спасибо Вам за ответ.
Мне, к сожалению, понадобилось немного более многословное объяснение,
которое я сам же и нашел в одном из форумов.
Просто Ваш замечательный код написан настолько оптимально,
что некоторые вещи выпадают из поля зрения такого новичка, как я.
Re[2]: Формы и потоки - я не понимаю...
От: vmpire Россия  
Дата: 11.12.12 19:33
Оценка:
Здравствуйте, .beginner, Вы писали:

Во первых, проверьте, что вы получаете свойство SynchronizationContext из формы, а не из рабочего потока.
Первый аргумент Post — это метод, который вы хотите вызвать асинхронно. Его сигнатура должна быть точно такой же, как SendOrPostCallback.
Например, создаёте в форме метод
void UpdateProgress(object donePercent) // именно object!
{
    myProgressBar.Value = (int)donePercent;
}


И вызываете Post из потока:

.Post(new SendOrPostCallback(UpdateProgress), 10)


Ещё пара способов:

Через Invoke (синхронно):
1. Создать метод в форме, который будет обновлять состояние контролов.
void UpdateProgress(int donePercent)
{
    myProgressBar.Value = donePercent;
}


2. Из рабочего потока вызывать этот метод через Form.Invoke

this.Invoke(new Action<int>(UpdateProgress), 23)



Через таски (асинхронно).
1. При создании параллельного потока передать туда шедулер формы:

var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext()


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

2. Из рабочего потока передавать информацию через новую таску в этом шедулере:

Task.Factory.StartNew(() => form.myProgressBar.Value = donePercent, uiScheduler)



Точный синтаксис не проверял, если что
Re[3]: Ну это ж надо!
От: vmpire Россия  
Дата: 11.12.12 19:38
Оценка:
Здравствуйте, .beginner, Вы писали:

B>То есть, раз колбэк происходит наружу относительно класса, в котором выполянется работа,

B>то и создавать его надо снаружи и передавать в класс, который делает работу, при его инициализации.
Неправильно, колбек — всего лишь указател на функцию, его всё равно где создавать.
Ключевое в вашем коде, что вы передаёте снаружи operation, которая создана в контексте формы.
Re: Формы и потоки - я не понимаю...
От: Nikolay_P_I  
Дата: 12.12.12 06:03
Оценка: 4 (2)
B>весь день я блуждал в лабиринтах Invoke, BeginInvoke, EndEnvoke,
B>а также AsyncOperationManager и пространства имен Tasks.Task.
B>Но у меня очень простая задача:
B>я хочу из виндовой формы запустить два потока.
B>Эти потоки что то делают.
B>Время от времени они мне в основной UI — поток рапортуют о результатах своей деятельности.

Правильный подход — отказаться от обновления UI из потоков обработки данных ВООБЩЕ. Совсем отказаться.

В потоках обработки обновляются объекты с информацией. В UI крутится таймер, что раз в примерно секунду достает из них информацию и обновляет UI. Всё. И просто и быстро. Поверьте — никому от того, что вы 10000 раз в секунду прогресс-бар обновите — лучше не станет. А будет только хуже, потому что тормозить начнет. Хотя если вы игрушку пишете с потребностью вывода в 60 fps — тогда там совсем иные технологии.
Re[2]: Формы и потоки - я не понимаю...
От: SteeLHeaD  
Дата: 12.12.12 08:18
Оценка:
Здравствуйте, Nikolay_P_I, Вы писали:

B>>весь день я блуждал в лабиринтах Invoke, BeginInvoke, EndEnvoke,

B>>а также AsyncOperationManager и пространства имен Tasks.Task.
B>>Но у меня очень простая задача:
B>>я хочу из виндовой формы запустить два потока.
B>>Эти потоки что то делают.
B>>Время от времени они мне в основной UI — поток рапортуют о результатах своей деятельности.

N_P>Правильный подход — отказаться от обновления UI из потоков обработки данных ВООБЩЕ. Совсем отказаться.


N_P>В потоках обработки обновляются объекты с информацией. В UI крутится таймер, что раз в примерно секунду достает из них информацию и обновляет UI. Всё. И просто и быстро. Поверьте — никому от того, что вы 10000 раз в секунду прогресс-бар обновите — лучше не станет. А будет только хуже, потому что тормозить начнет. Хотя если вы игрушку пишете с потребностью вывода в 60 fps — тогда там совсем иные технологии.


Здравствуйте.
Если говорит про UI — вы правы.
Но для меня это просто пример, на самом деле на месте UI будет управляющий поток, а там — чем быстрее, тем лучше.
Re[3]: Формы и потоки - я не понимаю...
От: Nikolay_P_I  
Дата: 12.12.12 08:51
Оценка:
SLH>Если говорит про UI — вы правы.
SLH>Но для меня это просто пример, на самом деле на месте UI будет управляющий поток, а там — чем быстрее, тем лучше.

А зачем тогда вам возня с контекстом ? В большинстве случаев простых lock() хватит.
Re[2]: Да пофиг как
От: Vaako Украина  
Дата: 12.12.12 08:52
Оценка:
Здравствуйте, .beginner, Вы писали:

B>Я бы хотел конкретизировать свой вопрос.

B>я сталкиваюсь с тем, что мне нужно из рабочего потока послать в UI — поток сообщение.
B>Для этого обычно используется метод Post ( что то вроде operation.Post )

Запоминаем контекст UI потока, как то так

private SynchronizationContext ctx;
private void buttonGO_Click(object sender, EventArgs e)
{
   ctx = SynchronizationContext.Current;
   ...
}


Ну и потом из любого потока:

string  _sError = "Ой";
ctx.Post( state =>
  {
     CallLostTables( "Отсутствуют необходимые таблицы");
     MessageBox.Show( _sError, "Ошибка в конфигурации БД", MessageBoxButtons.OK, MessageBoxIcon.Error );
            OnTaskError();
  }, null );
Re: Формы и потоки - я не понимаю...
От: B7_Ruslan  
Дата: 12.12.12 09:07
Оценка:
Если делать по простому, то надо использовать BackgroundWorker.
Он выполнит диспетчеризацию самостоятельно в винформс и впф.
Ссылка на BackgroundWorker должна обязательно хранится где-нибудь в форме на все время выполнения операции.
Как делать c task-ами мне объяснять влом.
Re: Формы и потоки - я не понимаю...
От: namespace  
Дата: 16.12.12 18:03
Оценка:
public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();            
    }
    
    void MainFormLoad(object sender, EventArgs e)
    {
        A a=new A();
        a.AEvent+=(s, evnt)=>
        {
            //------------------------------------------------
            this.BeginInvoke(new EventHandler(SetText),s, evnt);
            //------------------------------------------------
        };
        
        Action<A> action=delegate(A obj)
        {
            
            while(true)
            {
                obj.Foo();
                System.Threading.Thread.Sleep(1000);
            }
        };
        action.BeginInvoke(a, null,null);
    }
    
    void SetText(object sender, EventArgs e)
    {
        textBox1.Text= sender.ToString();
    }
}

class A
{
    public event EventHandler AEvent;
    public void Foo()
    {
        Random rnd=new Random();
        AEvent(rnd.Next().ToString(),null);
    }
}
Re[2]: Формы и потоки - я не понимаю...
От: Ziaw Россия  
Дата: 16.12.12 21:38
Оценка:
Здравствуйте, B7_Ruslan, Вы писали:

B_R>Ссылка на BackgroundWorker должна обязательно хранится где-нибудь в форме на все время выполнения операции.


для чего?
Re[2]: Формы и потоки - я не понимаю...
От: HowardLovekraft  
Дата: 17.12.12 06:42
Оценка: +1
Здравствуйте, Nikolay_P_I, Вы писали:

N_P>Правильный подход — отказаться от обновления UI из потоков обработки данных ВООБЩЕ. Совсем отказаться.

А обоснование будет?
Очень интересует, например, как такой подход с binding'ами подружить.
Re[3]: Формы и потоки - я не понимаю...
От: Vaako Украина  
Дата: 17.12.12 07:28
Оценка:
Здравствуйте, HowardLovekraft, Вы писали:

HL>Здравствуйте, Nikolay_P_I, Вы писали:


N_P>>Правильный подход — отказаться от обновления UI из потоков обработки данных ВООБЩЕ. Совсем отказаться.

HL>А обоснование будет?
HL>Очень интересует, например, как такой подход с binding'ами подружить.

Это нужно только из UI потока запускать?
OnPropertyChanged("myprop");

То в UI потоке
ctx = SynchronizationContext.Current;

а в другом потоку
ctx.Post( state =>
  {
     OnPropertyChanged("myprop");
  }, null );

В WinForm работает, а в WPF тоже вроде должно, хотя контексты там немного различаются если не вру.
Re[4]: Формы и потоки - я не понимаю...
От: HowardLovekraft  
Дата: 17.12.12 07:36
Оценка: +1
Здравствуйте, Vaako, Вы писали:

V>Это нужно только из UI потока запускать?

Причем здесь это?
НЯП, Nikolay_P_I предлагает менять в рабочих потоках нечто вроде модели, а потом по таймеру, который крутится в UI-потоке, "дергать" данные из этой модели и каким-то образом на их основе менять UI. Мне вот интересно зачем и каким образом (не в смысле "синхронизировать UI- и рабочий потоки", а архитектурно).
Re[3]: Формы и потоки - я не понимаю...
От: Nikolay_P_I  
Дата: 10.01.13 08:11
Оценка:
Здравствуйте, HowardLovekraft, Вы писали:

N_P>>Правильный подход — отказаться от обновления UI из потоков обработки данных ВООБЩЕ. Совсем отказаться.

HL>А обоснование будет?
HL>Очень интересует, например, как такой подход с binding'ами подружить.

А зачем ?

Для WinForm — совсем без них, напрямую лезем textBox.Text = ???;
Для WPF — апдейтим промежуточный объект state, куда и биндимся с формы.

Архитектурно довольно хорошо:

1) Красиво. Вся изменяемая визуализация сконцентрирована в 1 месте. Аккуратный такой списочек.
2) Работа с потоками проста, понятна и, следовательно, надежна.
3) Решена проблема обновления счетчика 15000 раз в секунду.

А какие минусы вы видите в таком подходе ?
Re[5]: Формы и потоки - я не понимаю...
От: Vaako Украина  
Дата: 10.01.13 18:45
Оценка:
Здравствуйте, HowardLovekraft, Вы писали:

HL>Здравствуйте, Vaako, Вы писали:


V>>Это нужно только из UI потока запускать?

HL>Причем здесь это?
HL>НЯП, Nikolay_P_I предлагает менять в рабочих потоках нечто вроде модели, а потом по таймеру, который крутится в UI-потоке, "дергать" данные из этой модели и каким-то образом на их основе менять UI. Мне вот интересно зачем и каким образом (не в смысле "синхронизировать UI- и рабочий потоки", а архитектурно).

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