Нужна помощь с доп. потоком, вернее с синхронизацией
От: mcka  
Дата: 14.10.13 06:50
Оценка:
Всем привет, уже третий день не могу разобраться с доп. потоком, вернее с синхронизацией. Нужна помощь, т.к. я только начал программировать на C# в VS2012.
Задача:
На форме две кнопки — "Start thread" и "Stop thread" и поле — richTextBox.
При нажатии на кнопку "Start thread" останавливается поток, если он был создан, иначе создается доп. поток в котором выводится инфа в richTextBox.
При нажатии на кнопку "Stop thread" поток должен корректно остановится, т.е. не убить поток, а в экстренном случаи должен завершить свою работу.
При закрытии приложении, если поток в работе необходимо опять же корректно остановить его.
Казалось все просто, но я столкнулся с проблемой: программа виснет.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication10
{
    delegate void SetTextCallback(string text);

    public partial class Form1 : Form
    {
        Thread th;

        public Form1()
        {
            InitializeComponent();
        }

        private void SetText(string text)
        {
            richTextBox1.AppendText(text + "\n");
        }

        public void SetTextSafe(string value)
        {
            if (richTextBox1.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                Invoke(d, new object[] { value + " (Invoke)" });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                SetText(value + " (No Invoke)");
            }
        }
        public void StopThread()
        {

            if (th != null)
            {
                th.Interrupt();

                th.Join(); // Здесь зависон !!!
 
                // если зделать, так:
                //while (th.Join(10) == false) { Application.DoEvents(); }
                // то казалось бы все решено, но из-за DoEvents() возникают другие проблеммы, такие как повторный вход в тот же метод StopThread()

                SetTextSafe("OK...");
                SetText("");
                th = null;
            }
        }

        public void StartThread()
        {
            StopThread();

            th = new Thread(new ThreadStart(this.ThreadProcSafe));
            SetTextSafe("GO...");
            th.Start();
        }

        private void ThreadProcSafe()
        {
            SetTextSafe("Start...");
            try
            {
                int i = 0;
                while (i < 1000)
                {
                    SetTextSafe(i.ToString());

                    i++;
                    Thread.Sleep(100);
                }
            }
            catch (ThreadInterruptedException)
            {
                SetTextSafe("terminate");
            }
            
            SetTextSafe("stop");
        }

        private void Safebutton_Click(object sender, EventArgs e)
        {
            StartThread();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            StopThread();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            StopThread();
        }
    }
}


Результат должен быть такой:
GO... (No Invoke)
Start... (Invoke)
0 (Invoke)
1 (Invoke)
2 (Invoke)
3 (Invoke)
4 (Invoke)
terminate (Invoke)
stop (Invoke)
OK... (No Invoke)

GO... (No Invoke)
Start... (Invoke)
0 (Invoke)
1 (Invoke)
2 (Invoke)
3 (Invoke)
4 (Invoke)
terminate (Invoke)
stop (Invoke)
OK... (No Invoke)


Как я понял зависон из-за th.Join();, т.к. он блокирует вызывающий поток до завершения доп. потока. Но при этом доп. поток хочет синхронно вывести инфу в richTextBox — "terminate" и "stop", а основной поток приостановлен (блокирован).
Пытался c AutoResetEvent autoEvent вместо th.Join(); использовал autoEvent.WaitOne(), результат тот же — зависон на autoEvent.WaitOne()
Re: Нужна помощь с доп. потоком, вернее с синхронизацией
От: ZloeBablo Германия  
Дата: 14.10.13 07:11
Оценка:
В вашем простейшем случае лучше сделать вот так:
Завести два поля:

Thread workingTask;
bool threadIsCanceled;


Запуская делать вот так:
workingTask = new Thread(UpdateTextInALoop);
workingTask.Start();
threadIsCanceled = false;


В функции которая выполняется в другом потоке:


private void UpdateTextInALoop()
        {
            try
            {
                int i = 0;
                while (i < 1000)
                {
                    SetTextSafe(i.ToString(CultureInfo.InvariantCulture));

                    i++;
                    Thread.Sleep(100);

                    ThowIfCanceled();
                }
            }
            catch (Exception ex)
            {
                //canceled
            }
        }

private void ThowIfCanceled()
        {            
            if (threadIsCanceled)
                throw new Exception();
        }


и чтобы остановить воток просто:

if (workingTask != null)
{
threadIsCanceled = true;
workingTask.Join();
workingTask = null;
}
Re: Нужна помощь с доп. потоком, вернее с синхронизацией
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 14.10.13 07:30
Оценка: 22 (1) +1
Здравствуйте, mcka, Вы писали:

M>Всем привет, уже третий день не могу разобраться с доп. потоком, вернее с синхронизацией. Нужна помощь, т.к. я только начал программировать на C# в VS2012.

M>Задача:
M>На форме две кнопки — "Start thread" и "Stop thread" и поле — richTextBox.
M>При нажатии на кнопку "Start thread" останавливается поток, если он был создан, иначе создается доп. поток в котором выводится инфа в richTextBox.
M>При нажатии на кнопку "Stop thread" поток должен корректно остановится, т.е. не убить поток, а в экстренном случаи должен завершить свою работу.
M>При закрытии приложении, если поток в работе необходимо опять же корректно остановить его.
M>Казалось все просто, но я столкнулся с проблемой: программа виснет.

Это не задача, это уже решение. Вот что ты хочешь на самом деле, это другой вопрос.

Если по теме, то попробуй заменить Control.Invoke, на Control.BeginInvoke, чтобы обновление UI сделать асинронно.

Вообще же, я бы очень рекомендовал отказаться от такого подхода. Во-первых, ручное создание потоков — дело довольно накладное и плохо масштабируемое. Например, ты откроешь десяток таких форм, что приведет к созданию 10 потоков. Это не смертельно (хотя по ресурсам накладно), но если в этой форме ты будешь выполнять вычислительные операции в этом фоновом потоке, то в этом случае мы столкнемся с недостатком ресурсов, ведь оптимальное число рабочих потоков для вычислений равняется числу ядер процессора. Если в этой форме выполняеются асинхронные операции, то падения производительности не будет (такой сильно, как в первом случае), но все равно решение не очень.

Я бы сразу порекомендовал посмотреть на TPL, и сразу же отделять логику от представления. Многопоточность сама по себе довольно сложная тема, при этом очень здорово, чтобы была возможность думать о ней в изоляции.

Так, например, если у нас есть длительные вычисления, то выделяем эту логику в класс, с методов, возаращающим Task<YourResult>:

class BusinessLogic
{
  public Task<YourResults> GetResultsAsync()
  {
    // Используем любой способ получения результата.
    // Это может быть операция ввода-вывода и чтения данных из базы,
    // это может быть вычислительная операция, запущенная с помощью
    // Task.Factory.StartNew или Task.Run.
    // Это может быть составная асинхронная операция, полученная путем комбинации
    // других асинхронных операций с помощью await.
    
    // Если придусмотрена в этой операции отмена, то метод может принимать CancellationToken!
  }
}

class Form1
{
   private void btn_Click(object s, EventArgs e)
   {
      // Если есть C# 5.0, то используем async/await
      // в противном случае, работаем в рукопашную
      var task = businessLogic.GetResultsAsync();
      task.ContinueWith(r => textBox.Text = r.Result, 
          // "продолжение" задачи будет исполняться в потоке UI, никакая синхронизация
          // не нужна
          TaskScheduler.FromSynchronizationContext);
   
      // Если мы хотим поддержать возможность отмены операции, то сохраняем 
      // CancellationToken в поле формы, затем при закрытии дергаем его для
      // отмены операции
   }
}


M>Как я понял зависон из-за th.Join();, т.к. он блокирует вызывающий поток до завершения доп. потока. Но при этом доп. поток хочет синхронно вывести инфу в richTextBox — "terminate" и "stop", а основной поток приостановлен (блокирован).

M>Пытался c AutoResetEvent autoEvent вместо th.Join(); использовал autoEvent.WaitOne(), результат тот же — зависон на autoEvent.WaitOne()

Да, это классический dead lock, избежать которого без изменения задачи довольно сложно. В любом случае, в текущем решеии слишком много всего намешано в одном месте, поэтому с точки зрения сопровождаемости и тестируемости это решение будет очень тяжеловесным.
Re: Нужна помощь с доп. потоком, вернее с синхронизацией
От: Sinix  
Дата: 14.10.13 07:31
Оценка:
Здравствуйте, mcka, Вы писали:

M>Всем привет, уже третий день не могу разобраться с доп. потоком, вернее с синхронизацией. Нужна помощь, т.к. я только начал программировать на C# в VS2012.


1. Заменить в SetTextSafe Invoke() на BeginInvoke() — замена текста будет происходить асинхронно, потоки не будут блокировать друг друга.

2. По возможности уйти от схемы с .Join/.Interrupt(). Сам видишь, даже на простом примере легко вляпаться в дедлок. В идеале общая схема такая: StopThread просто просит фоновый поток прервать свою работу (например, изменяет значение поля cancel на True). Фоновый поток периодически проверяет значение этого поля и, если cancel == true, прерывает свою работу и оповещает об этом основной поток.

Для начала и то что есть более чем неплохо, после разбирательств с thread можно посмотреть в сторону Task + CancellationToken, с ними задача решается чуть проще.
Re[2]: Нужна помощь с доп. потоком, вернее с синхронизацией
От: mcka  
Дата: 14.10.13 07:38
Оценка:
Здравствуйте, ZloeBablo,
Ваш пример тоже не рабочий. Доп. поток не сможет вывести инфу ("terminate (Invoke)" и "stop (Invoke)") в richTextBox1, после того как он получит уведомление threadIsCanceled = true;
Re[2]: Нужна помощь с доп. потоком, вернее с синхронизацией
От: mcka  
Дата: 14.10.13 07:42
Оценка:
Здравствуйте, ZloeBablo, Вы писали:

И вторая ошибка: поле bool threadIsCanceled; не прервет метод Thread.Sleep(100);, поэтому я использую th.Interrupt();

Вопрос открыт.
Re[3]: Нужна помощь с доп. потоком, вернее с синхронизацией
От: ZloeBablo Германия  
Дата: 14.10.13 07:47
Оценка:
Здравствуйте, mcka, Вы писали:

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


M>И вторая ошибка: поле bool threadIsCanceled; не прервет метод Thread.Sleep(100);, поэтому я использую th.Interrupt();


M>Вопрос открыт.


Какой смысл прерывать Sleep на 100 мс и ниже использовать Join... ?
Re[3]: Нужна помощь с доп. потоком, вернее с синхронизацией
От: ZloeBablo Германия  
Дата: 14.10.13 07:47
Оценка:
Здравствуйте, mcka, Вы писали:

M>Здравствуйте, ZloeBablo,

M>Ваш пример тоже не рабочий. Доп. поток не сможет вывести инфу ("terminate (Invoke)" и "stop (Invoke)") в richTextBox1, после того как он получит уведомление threadIsCanceled = true;

Я думаю вы сможете изменить код так чтобы он смог это сделать.
Я всего лишь показал как работает отмена рабочего потока без Abort или Interrupt. Последние два метода не рекомендованны к использованию да и конструкция join тоже не лучшее решение этой проблемы.
Если вам надо любой ценой заставить заработать ваш код, то просто заменить Invoke на BeginInvoke.
Re[2]: Нужна помощь с доп. потоком, вернее с синхронизацией
От: mcka  
Дата: 14.10.13 07:53
Оценка:
Здравствуйте, Sinix, Вы писали:

S>1. Заменить в SetTextSafe Invoke() на BeginInvoke() — замена текста будет происходить асинхронно, потоки не будут блокировать друг друга.

А за это спасибо !!! Работает!

2. Про Task я много где видел, что рекомендуют использовать их в место thead, но они появились только в .NET Framework 4

3. Выше я писал почему я использую Join.Interrupt() вместо поля.
Re: Нужна помощь с доп. потоком, вернее с синхронизацией
От: mcka  
Дата: 14.10.13 08:06
Оценка:
Всем спасибо, за помощь и быстрые ответы.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.