Господа,
весь день я блуждал в лабиринтах Invoke, BeginInvoke, EndEnvoke,
а также AsyncOperationManager и пространства имен Tasks.Task.
Но у меня очень простая задача:
я хочу из виндовой формы запустить два потока.
Эти потоки что то делают.
Время от времени они мне в основной UI — поток рапортуют о результатах своей деятельности.
И я до сих пор расшибаю лоб о то, что вызов из параллельного потока приводит к сообщению
"Недопустимая операция в нескольких потоках: попытка доступа к элементу
управления "textBox1" не из того потока, в котором он был создан".
Есть какой то способ из параллельного потока передать данные в основной поток?
У деда мороза этого попросить, что ли...
Я бы хотел конкретизировать свой вопрос.
я сталкиваюсь с тем, что мне нужно из рабочего потока послать в UI — поток сообщение.
Для этого обычно используется метод Post ( что то вроде operation.Post )
Я не понимаю, что за первый аргумент у функции Post ( по смыслу — это callback ) и как его создать.
по примеру http://www.gotdotnet.ru/files/254/ я этого понять не могу.
Если я смогу этот пример заставить заработать — я считаю что для меня задача решена.
Все получилось.
Как обычно, через задницу — в смысле, нет нормального объяснения, оттого и путаница.
Ответ содержится тут :
а именно в строчках
// 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;
}
Здравствуйте, QrystaL, спасибо Вам за ответ.
Мне, к сожалению, понадобилось немного более многословное объяснение,
которое я сам же и нашел в одном из форумов.
Просто Ваш замечательный код написан настолько оптимально,
что некоторые вещи выпадают из поля зрения такого новичка, как я.
Во первых, проверьте, что вы получаете свойство SynchronizationContext из формы, а не из рабочего потока.
Первый аргумент Post — это метод, который вы хотите вызвать асинхронно. Его сигнатура должна быть точно такой же, как SendOrPostCallback.
Например, создаёте в форме метод
Здравствуйте, .beginner, Вы писали:
B>То есть, раз колбэк происходит наружу относительно класса, в котором выполянется работа, B>то и создавать его надо снаружи и передавать в класс, который делает работу, при его инициализации.
Неправильно, колбек — всего лишь указател на функцию, его всё равно где создавать.
Ключевое в вашем коде, что вы передаёте снаружи operation, которая создана в контексте формы.
B>весь день я блуждал в лабиринтах Invoke, BeginInvoke, EndEnvoke, B>а также AsyncOperationManager и пространства имен Tasks.Task. B>Но у меня очень простая задача: B>я хочу из виндовой формы запустить два потока. B>Эти потоки что то делают. B>Время от времени они мне в основной UI — поток рапортуют о результатах своей деятельности.
Правильный подход — отказаться от обновления UI из потоков обработки данных ВООБЩЕ. Совсем отказаться.
В потоках обработки обновляются объекты с информацией. В UI крутится таймер, что раз в примерно секунду достает из них информацию и обновляет UI. Всё. И просто и быстро. Поверьте — никому от того, что вы 10000 раз в секунду прогресс-бар обновите — лучше не станет. А будет только хуже, потому что тормозить начнет. Хотя если вы игрушку пишете с потребностью вывода в 60 fps — тогда там совсем иные технологии.
Здравствуйте, 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 будет управляющий поток, а там — чем быстрее, тем лучше.
SLH>Если говорит про UI — вы правы. SLH>Но для меня это просто пример, на самом деле на месте UI будет управляющий поток, а там — чем быстрее, тем лучше.
А зачем тогда вам возня с контекстом ? В большинстве случаев простых lock() хватит.
Здравствуйте, .beginner, Вы писали:
B>Я бы хотел конкретизировать свой вопрос. B>я сталкиваюсь с тем, что мне нужно из рабочего потока послать в UI — поток сообщение. B>Для этого обычно используется метод Post ( что то вроде operation.Post )
Если делать по простому, то надо использовать BackgroundWorker.
Он выполнит диспетчеризацию самостоятельно в винформс и впф.
Ссылка на BackgroundWorker должна обязательно хранится где-нибудь в форме на все время выполнения операции.
Как делать c task-ами мне объяснять влом.
Здравствуйте, Nikolay_P_I, Вы писали:
N_P>Правильный подход — отказаться от обновления UI из потоков обработки данных ВООБЩЕ. Совсем отказаться.
А обоснование будет?
Очень интересует, например, как такой подход с binding'ами подружить.
Здравствуйте, 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 тоже вроде должно, хотя контексты там немного различаются если не вру.
Здравствуйте, Vaako, Вы писали:
V>Это нужно только из UI потока запускать?
Причем здесь это?
НЯП, Nikolay_P_I предлагает менять в рабочих потоках нечто вроде модели, а потом по таймеру, который крутится в UI-потоке, "дергать" данные из этой модели и каким-то образом на их основе менять UI. Мне вот интересно зачем и каким образом (не в смысле "синхронизировать UI- и рабочий потоки", а архитектурно).
Здравствуйте, HowardLovekraft, Вы писали:
N_P>>Правильный подход — отказаться от обновления UI из потоков обработки данных ВООБЩЕ. Совсем отказаться. HL>А обоснование будет? HL>Очень интересует, например, как такой подход с binding'ами подружить.
А зачем ?
Для WinForm — совсем без них, напрямую лезем textBox.Text = ???;
Для WPF — апдейтим промежуточный объект state, куда и биндимся с формы.
Архитектурно довольно хорошо:
1) Красиво. Вся изменяемая визуализация сконцентрирована в 1 месте. Аккуратный такой списочек.
2) Работа с потоками проста, понятна и, следовательно, надежна.
3) Решена проблема обновления счетчика 15000 раз в секунду.
Здравствуйте, HowardLovekraft, Вы писали:
HL>Здравствуйте, Vaako, Вы писали:
V>>Это нужно только из UI потока запускать? HL>Причем здесь это? HL>НЯП, Nikolay_P_I предлагает менять в рабочих потоках нечто вроде модели, а потом по таймеру, который крутится в UI-потоке, "дергать" данные из этой модели и каким-то образом на их основе менять UI. Мне вот интересно зачем и каким образом (не в смысле "синхронизировать UI- и рабочий потоки", а архитектурно).
Потому и вылетает, что контексты у потоков разные. Все пользовательские элементы управления могут изменяться только из одного единственного потока и пофиг как при этом организованы другие потоки и сколько их архитектурно или как-либо еще.