Здравствуйте, montegava, Вы писали:
M>Уважаемые форумчане.
M>Есть такая задача. M>В программе создается некий класс расчетов, в котором вызывается функция расчетов в потоке.
M>
M> new Thread(new ThreadStart(myFunc)).Start();
M>
M>Хочу добавить прогресс бар на основную форму, для вывода процентов по расчетам. M>Как сделать лучше?
M>Через делегаты? Через сообщения с потока вычесления? M>Вобщем — хотел бы услышать ваше мнение, как обычно делаете в таких случаях вы? M>Заранее спасибо.
BackgroundWorker. С потоками будут изначально проблемы. Особенно с изменением статуса на форме. .Net будет выкидывать эксепшен при любой попытке это сделать.
Здравствуйте, montegava, Вы писали:
M>Уважаемые форумчане.
M>Есть такая задача. M>В программе создается некий класс расчетов, в котором вызывается функция расчетов в потоке.
M>
M> new Thread(new ThreadStart(myFunc)).Start();
M>
M>Хочу добавить прогресс бар на основную форму, для вывода процентов по расчетам. M>Как сделать лучше?
M>Через делегаты? Через сообщения с потока вычесления? M>Вобщем — хотел бы услышать ваше мнение, как обычно делаете в таких случаях вы? M>Заранее спасибо.
new Task(...).Start();
Взаимодействовать с GUI из другого потока придется через Invoke(), BeginInvoke()...
Здравствуйте, montegava, Вы писали:
M>Хочу добавить прогресс бар на основную форму, для вывода процентов по расчетам. M>Как сделать лучше?
M>Через делегаты? Через сообщения с потока вычесления? M>Вобщем — хотел бы услышать ваше мнение, как обычно делаете в таких случаях вы? M>Заранее спасибо.
Посмотри вот на эту статью: Reporting Progress from Tasks. Там есть примеры как с BackgroundWorker-ом, так и с Task-ами.
Кроме того, при работе с Task-ами совсем не обязательно обновлять данные на форме через Invoke, достаточно воспользоваться TaskScheduler.FromCurrentSynchronizationContext.
Вот простой пример: у нас есть форма с кнопкой, при нажатии на которую мы запускаем длительную операцию и результат этой операции (который является строкой) отображаем в label-е на форме. В этом случае continuation задачи будет вызван в правильном контексте синхронизации, так что в нем можно спокойно обращаться к элементам UI.
Здравствуйте, montegava, Вы писали:
M>Хм ... а можно ли обойтись без заданий и вёкеров? M>Дело в том, что у меня поток уже создается как
M>
M> new Thread(new ThreadStart(myFunc)).Start();
M>
M>Могу ли я внутри моей функции (myFunc), которая выполняется в потоке, вызвать перерисовку прогрессбара?
Лучше так не делать, все таки необходимо разделять уровень Вью и бизнес логики, или что то выше него.
Если не хотите использовать BackgroundTaskWorker и другие готовые наработки, то вы конечно можете сделать свой
"велосипед"
class FuncHolder{
void Run();
Action<int> ProgressChanged;
Action<int> Completed;
}
это так сказать, "бюджетный" вариант для ленивых
Вызывайте делегаты ProgressChanged и Completed, через контекст синхронихации ВинФорм приложения и будет все чудесно работать.
Однако я бы вам посоветовал использовать узнаваемый шаблон
BackgroundTaskWorker или реализовать свой FuncHolder по всем правилам Событийного асинхронного шаблона
Здравствуйте, montegava, Вы писали:
M>Хм ... а можно ли обойтись без заданий и вёкеров? M>Дело в том, что у меня поток уже создается как
M>
M> new Thread(new ThreadStart(myFunc)).Start();
M>
M>Могу ли я внутри моей функции (myFunc), которая выполняется в потоке, вызвать перерисовку прогрессбара?
Можно сделать что-то такое:
public partial class Form1 : Form
{
Thread thread;
public Form1()
{
InitializeComponent();
thread = new Thread(LongRunningTask);
thread.Start();
}
private void LongRunningTask()
{
Console.WriteLine("Starting long running task... ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 100; i++)
{
ChangeProgress(i);
Thread.Sleep(100);
}
Console.WriteLine("Long running task finished...");
}
private void ChangeProgress(int progress)
{
Console.WriteLine("Changing progress. value = {0}", progress);
Action changeProgress = () => progressBar1.Value = progress;
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke(changeProgress);
}
else
{
changeProgress();
}
// Или вместо предыдущих 9 строк можно использовать вот такую конструкцию,
// которая спрячет всю нужную логику по вызову Invoke-а в случае необходимости
// progressBar1.InvokeIfNeeded(() => progressBar1.Value = progress);
}
}
А вот класс с расширением:
/// <summary>
/// Расширения облегчающие работу с элементами управления в многопоточной среде.
/// </summary>public static class ControlExtentions
{
/// <summary>
/// Вызов делегата через control.Invoke, если это необходимо.
/// </summary>
/// <param name="control">Элемент управления</param>
/// <param name="doit">Делегат с некоторым действием</param>public static void InvokeIfNeeded(this Control control, Action doit)
{
if (control.InvokeRequired)
control.Invoke(doit);
else
doit();
}
/// <summary>
/// Вызов делегата через control.Invoke, если это необходимо.
/// </summary>
/// <typeparam name="T">Тип параметра делегата</typeparam>
/// <param name="control">Элемент управления</param>
/// <param name="doit">Делегат с некоторым действием</param>
/// <param name="arg">Аргумент делагата с действием</param>public static void InvokeIfNeeded<T>(this Control control, Action<T> doit, T arg)
{
if (control.InvokeRequired)
control.Invoke(doit, arg);
else
doit(arg);
}
}
Я когда-то вот тут писал о взаимодействии с UI из других потоков, поэтому если непонятна именно эта тема, то велкам.
Во-первых, Invoke и так реализован через SynchronizationContext, во-вторых, нам нужна дополнительная переменная, в которой мы сохраним текущий контекст, а в-третьих, нам все еще нужно проверять на предмет control.InvokeRequired, поскольку нам не нужно использовать SynchronizationContext, если мы вызываем эту функцию из потока UI.
В качестве сравнения, вот что у меня получилось при использовании SynchronizationContext (возможно я просто не умею его готовить) (см. код ниже).
И еще, я подсмотрел это решение у Билла Вагнера в его Effective C# (или More Effective C#, точно уже не помню), вот так и повелось
public partial class Form1 : Form
{
Thread thread;
SynchronizationContext synchronizationContext;
public Form1()
{
// Теперь нам нужно сохранить контекст синхронизации
synchronizationContext = SynchronizationContext.Current;
InitializeComponent();
thread = new Thread(LongRunningTask);
thread.Start();
}
private void ChangeProgress(int progress)
{
// Здесь мы же не можем использовать текущий контекст синхронизации,
// поскольку он равен null, кроме того, нам все равно не помешало бы
// понять, а вообще нужно нам использовать контекст синхронизации или нет
synchronizationContext.Post(o => progressBar1.Value = progress, null);
}
}
Здравствуйте, SergeyT., Вы писали:
ST>Во-первых, Invoke и так реализован через SynchronizationContext,
Наоборот, SynchronizationContext POST — враппит Control.BeginInvoke,а SEND — Control.Invoke
ST>во-вторых, нам нужна дополнительная переменная, в которой мы сохраним текущий контекст,
Ну это не та проблема. По хорошему вообще отделять ГУИ от реализации многопоточного кода.
Мне кажется, что большим злом является ваш захват переменной. ST>а в-третьих, нам все еще нужно проверять на предмет control.InvokeRequired, поскольку нам не нужно использовать SynchronizationContext, если мы вызываем эту функцию из потока UI.
Если из потока ЮИ мы не хотим менять пропсу Прогрессбара напрямую, а делать это через специальный метод, то я бы напсиал во так
public partial class Form1 : Form
{
Thread thread;
SynchronizationContext synchronizationContext;
public Form1()
{
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
thread = new Thread(LongRunningTask);
thread.Start();
}
private void RefreshProgress(object progress) // это для вызова через Пост/Сенд
{
RefreshProgress((int)progress);
}
private void RefreshProgress(int progress) // это для вызова из потока ЮИ напрямую
{
progressBar1.Value = (int)progress;
}
private void LongRunningTask()
{
Console.WriteLine("Starting long running task... ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 100; i++)
{
ChangeProgress(i);
Thread.Sleep(100);
}
Console.WriteLine("Long running task finished...");
}
private void ChangeProgress(int progress)
{
Console.WriteLine("Changing progress. value = {0}", progress);
synchronizationContext.Post(RefreshProgress, progress);
/*
Action<int> act = new Action<int>(RefreshProgress);
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke(act, progress);
}
else
{
//changeProgress();
RefreshProgress(progress);
}
* */
}
}