Потоки в C#. Как лучше?
От: montegava  
Дата: 29.03.11 21:15
Оценка:
Уважаемые форумчане.

Есть такая задача.
В программе создается некий класс расчетов, в котором вызывается функция расчетов в потоке.

            new Thread(new ThreadStart(myFunc)).Start();


Хочу добавить прогресс бар на основную форму, для вывода процентов по расчетам.
Как сделать лучше?

Через делегаты? Через сообщения с потока вычесления?
Вобщем — хотел бы услышать ваше мнение, как обычно делаете в таких случаях вы?
Заранее спасибо.
Re: Потоки в C#. Как лучше?
От: Andrey Rubayko  
Дата: 29.03.11 21:23
Оценка: 1 (1) +1
Здравствуйте, montegava, Вы писали:

M>Уважаемые форумчане.


M>Есть такая задача.

M>В программе создается некий класс расчетов, в котором вызывается функция расчетов в потоке.

M>
M>            new Thread(new ThreadStart(myFunc)).Start();
M>


M>Хочу добавить прогресс бар на основную форму, для вывода процентов по расчетам.

M>Как сделать лучше?

M>Через делегаты? Через сообщения с потока вычесления?

M>Вобщем — хотел бы услышать ваше мнение, как обычно делаете в таких случаях вы?
M>Заранее спасибо.

BackgroundWorker. С потоками будут изначально проблемы. Особенно с изменением статуса на форме. .Net будет выкидывать эксепшен при любой попытке это сделать.
Re: Потоки в C#. Как лучше?
От: gromozeka1974 Украина  
Дата: 30.03.11 05:18
Оценка: 1 (1)
Здравствуйте, montegava, Вы писали:

M>Уважаемые форумчане.


M>Есть такая задача.

M>В программе создается некий класс расчетов, в котором вызывается функция расчетов в потоке.

M>
M>            new Thread(new ThreadStart(myFunc)).Start();
M>


M>Хочу добавить прогресс бар на основную форму, для вывода процентов по расчетам.

M>Как сделать лучше?

M>Через делегаты? Через сообщения с потока вычесления?

M>Вобщем — хотел бы услышать ваше мнение, как обычно делаете в таких случаях вы?
M>Заранее спасибо.

new Task(...).Start();
Взаимодействовать с GUI из другого потока придется через Invoke(), BeginInvoke()...
Re: Потоки в C#. Как лучше?
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 30.03.11 07:52
Оценка:
Здравствуйте, montegava, Вы писали:

M>Хочу добавить прогресс бар на основную форму, для вывода процентов по расчетам.

M>Как сделать лучше?

M>Через делегаты? Через сообщения с потока вычесления?

M>Вобщем — хотел бы услышать ваше мнение, как обычно делаете в таких случаях вы?
M>Заранее спасибо.

Посмотри вот на эту статью: Reporting Progress from Tasks. Там есть примеры как с BackgroundWorker-ом, так и с Task-ами.

Кроме того, при работе с Task-ами совсем не обязательно обновлять данные на форме через Invoke, достаточно воспользоваться TaskScheduler.FromCurrentSynchronizationContext.
Вот простой пример: у нас есть форма с кнопкой, при нажатии на которую мы запускаем длительную операцию и результат этой операции (который является строкой) отображаем в label-е на форме. В этом случае continuation задачи будет вызван в правильном контексте синхронизации, так что в нем можно спокойно обращаться к элементам UI.

(пример взят из Джозеф Албахари. Часть 5.2. Параллельное программирование)


public partial class Form1 : Form
{
    TaskScheduler _uiScheduler; 

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Console.WriteLine("button click. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        // Get the UI scheduler for the thread that created the form:
        _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

        Task.Factory.StartNew<string>(LongRunningTask)
            .ContinueWith(ant => this.label.Text = ant.Result, _uiScheduler);

    }

    private string LongRunningTask()
    {
        Console.WriteLine("Starting long running task... ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(2000);
        Console.WriteLine("Long running task finished...");
        return "Result";
    }
}
Re[2]: Потоки в C#. Как лучше?
От: montegava  
Дата: 30.03.11 09:52
Оценка:
Хм ... а можно ли обойтись без заданий и вёкеров?
Дело в том, что у меня поток уже создается как

  new Thread(new ThreadStart(myFunc)).Start();


Могу ли я внутри моей функции (myFunc), которая выполняется в потоке, вызвать перерисовку прогрессбара?
Re[3]: Потоки в C#. Как лучше?
От: LastExile Украина  
Дата: 30.03.11 10:14
Оценка:
Здравствуйте, 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 по всем правилам Событийного асинхронного шаблона
Re[3]: Потоки в C#. Как лучше?
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 30.03.11 10:29
Оценка: 1 (1)
Здравствуйте, 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 из других потоков, поэтому если непонятна именно эта тема, то велкам.
Re[4]: Потоки в C#. Как лучше?
От: LastExile Украина  
Дата: 30.03.11 10:42
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>А вот класс с расширением:

ST>
ST>/// <summary>
ST>/// Расширения облегчающие работу с элементами управления в многопоточной среде.
ST>/// </summary>
ST>public static class ControlExtentions
ST>{

ST>    /// <summary>
ST>    /// Вызов делегата через control.Invoke, если это необходимо.
ST>    /// </summary>
ST>    /// <param name="control">Элемент управления</param>
ST>    /// <param name="doit">Делегат с некоторым действием</param>
ST>    public static void InvokeIfNeeded(this Control control, Action doit)
ST>    {
ST>        if (control.InvokeRequired)
ST>            control.Invoke(doit);
ST>        else
ST>            doit();
ST>    }

ST>    /// <summary>
ST>    /// Вызов делегата через control.Invoke, если это необходимо.
ST>    /// </summary>
ST>    /// <typeparam name="T">Тип параметра делегата</typeparam>
ST>    /// <param name="control">Элемент управления</param>
ST>    /// <param name="doit">Делегат с некоторым действием</param>
ST>    /// <param name="arg">Аргумент делагата с действием</param>
ST>    public static void InvokeIfNeeded<T>(this Control control, Action<T> doit, T arg)
ST>    {
ST>        if (control.InvokeRequired)
ST>            control.Invoke(doit, arg);
ST>        else
ST>            doit(arg);
ST>    }
ST>}
ST>


А почему вы не использовали
SynchronizationContext.Post
веместо


if (control.InvokeRequired)
   control.Invoke(doit, arg);
else
   doit(arg);
Re[5]: Потоки в C#. Как лучше?
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 30.03.11 11:12
Оценка:
Здравствуйте, LastExile, Вы писали:

LE>А почему вы не использовали

LE>SynchronizationContext.Post
LE>веместо

Можно на "ты"

LE>
LE>if (control.InvokeRequired)
LE>   control.Invoke(doit, arg);
LE>else
LE>   doit(arg);
LE>


Во-первых, 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);
    }
}
Re[6]: Потоки в C#. Как лучше?
От: LastExile Украина  
Дата: 30.03.11 12:45
Оценка:
Здравствуйте, 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);
            }    
             * */
        }
    }
Re[4]: Потоки в C#. Как лучше?
От: montegava  
Дата: 31.03.11 06:03
Оценка:
Спасибо всем, кто откликнулся. Все что надо было — успешно закодил, благодаря вашим коментам и статье о впф.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.