Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Блокируется BackgroundWorker  (Прочитано 20981 раз)
0 Пользователей и 1 Гость смотрят эту тему.
h.m.f.
Участник

ru
Offline Offline
Пол: Мужской

« : 24-12-2010 06:24 » 

Из wpf формы стартует BackgroundWorker:
Код:
 
backgroundWorker.RunWorkerAsync();

private void backgroupWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //Scalper.StopWork = backgroundWorker.CancellationPending;
            Scalper.Run();
            if (backgroundWorker.CancellationPending)
            {               
                e.Cancel = true;               
                return;
            }
        }

который выполняет метод:
Код:
private void Run()
        {
           
            for (int i = 0; i < 300; i++)
            {
                if (!stopWork)
                {                   
                    SummInfo.TransactionCount++;                   
                }
                else
                {
                    return;
                }
            }


 public int TransactionCount
        {
            get
            {
                return transactionCount;
            }
            set
            {
                if (value != transactionCount)
                {     
                    // метод формы wpf
                    base.NotifyObservers(this, Observer.ListenersCollection.SummarizeInfoDisplayer);
                }
                transactionCount=value;

            }
        }

public void Notify(object anObject, Observer.ListenersCollection listener)
        {
List<SummarizeInformationRow> SummInfoTable = GetSummarizeInfoTable((SummarizeInformation)anObject);
                    if (Dispatcher.Thread != Thread.CurrentThread)
                    {                       
                     
                           Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                                new Action(
                                    delegate()
                                    {
                                        lvSummarizeInfo.ItemsSource = SummInfoTable;
                                    }
                            ));                     
                    }
}



при останове потока методом backgroundWorker.CancelAsync() возникает неприятность:
Код:
private void btnStopRobot_Click(object sender, RoutedEventArgs e)
        {
            backgroundWorker.CancelAsync();
            Scalper.StopWork = backgroundWorker.CancellationPending;
                       
            lbStatusInfo.Items.Add("Остановлен");
            btnStartRobot.IsEnabled = true;

            btnPauseRobot.IsEnabled = false;
            btnStopRobot.IsEnabled = false;


            //while (backgroundWorker.IsBusy) // IsBusy не становится false
            {               
            }

            Scalper = null;
           
            lbStatusInfo.Items.Clear();
            FillSummarizeInfoControl();

           
            lvSummarizeInfo.Items.Clear(); // возникает ошибка
            // Операция недопустима, когда ItemsSource используется.
            // Вместо этого получите доступ и измените элементы с помощью ItemsControl.ItemsSource.
        }

Подскажите, пожалуйста, что блокирует поток?
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #1 : 24-12-2010 09:07 » 

По-моему дано довольно мутное и неполное описание проблемы. Например, проблема указана для lvSummarizeInfo.Items, которые либо выше нигде не встречаются, либо непонятно как привязаны к фоновому потоку.

h.m.f., попробуй ещё раз, только разложи по полочкам. И не забывай объяснять текстом (а не кодом), что где делается.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
h.m.f.
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #2 : 24-12-2010 10:39 » 

Да уж действительно, перечитал свой пост - сам ничего не понял.
Теперь по полочкам: в общем есть форма с которой запускается в другом потоке метод (Run) другого класса (Scalper), изменяющий одно из полей класса Scalper (SummarizeInfo.TransactionCount)
Задача состоит в том, что при изменении переменной SummaraizeInfo.TransactionCount синхронно должен обновиться соответствующий контрол wpf формы:
lvSummarizeInfo - listview. Но в данном случае, какой именно контрол должен обновиться не суть важно, т.к. блокировка потока происходит и после замены listview на label.

Для обвновления контрола был написан метод Notify класса формы, который сейчас выглядит так:
Код:
public void Notify(object anObject, Observer.ListenersCollection listener)
        {
if (!lblTransactionCount.Dispatcher.CheckAccess())
                        {
                            lblTransactionCount.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                                new Action(
                                    delegate()
                                    {
                                        lblTransactionCount.Content = ((SummarizeInformation)anObject).TransactionCount.ToString();
                                    }
                            ));
                        }
}

После останова backgroundWorker контрол должен обнулиться. Но останова не происходит (постоянно IsBusy=true)

Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 24-12-2010 11:55 » 

h.m.f., теперь давай по полочкам:

1) Есть форма, из которой по некотором событиям запускается и останавливается поток в объекте класса Scalper. Ещё на форме есть элемент управления, который должен обновляться при возникновении у объекта класса Scalper какого-то события.

2) Есть элемент управления, который обновляется, получая сообщение от формы.

3) Наконец, есть объект класса Scalper, который из основного потока запускает и останавливает параллельный поток. В параллельном потоке этот объект изменяет свой атрибут, что является событием объекта. После указания завершить параллельный поток, объект Scalper сбрасывает значение атрибута и генерирует последнее событие. (Естественно, обработку событий выполняет этот же параллельный поток, в котором они возникают.)

Т.е. как-то так:
Код: (C#)
using System;
using System.Threading;

namespace Problem
{
    class Scalper
    {
        public class ScalperEventArgs : EventArgs
        {
            private ulong counter;

            public ScalperEventArgs(ulong counter) :
                base()
            {
                this.counter = counter;
            }

            public ulong Counter
            {
                get
                {
                    return this.counter;
                }
            }
        }

        private Thread thread;
        private ulong counter;
        private bool running;

        public Scalper()
        {
            this.thread = null;
            this.counter = 0;
            this.running = false;
        }

        public event EventHandler<ScalperEventArgs> Event;

        private void OnEvent()
        {
            if (this.Event != null)
            {
                this.Event(this, new ScalperEventArgs(this.counter));
            }
        }

        public void Begin()
        {
            this.thread = new Thread(new ThreadStart(this.Run));
            this.running = true;
            this.thread.Start();
        }

        public void End()
        {
            this.running = false;
            this.thread.Join();
            this.thread = null;
        }

        private void Run()
        {
            while (this.running)
            {
                this.counter += 1;
                this.OnEvent();
            }
            this.counter = 0;
            this.OnEvent();
        }
    }

    class Control
    {
        public void Update(ulong counter)
        {
            Console.WriteLine("Control.Update: {0}", counter);
        }
    }

    class Form
    {
        private Scalper scalper;
        private Control control;

        public Form(Scalper scalper)
        {
            this.scalper = scalper;
            this.control = new Control();
            this.scalper.Event += new EventHandler<Scalper.ScalperEventArgs>(scalper_Event);
        }

        void scalper_Event(object sender, Scalper.ScalperEventArgs e)
        {
            this.control.Update(e.Counter);
        }

        public void Run()
        {
            this.scalper.Begin();
            Console.ReadKey();
            this.scalper.End();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Scalper scalper = new Scalper();
            Form form = new Form(scalper);
            form.Run();
        }
    }
}

И вроде всё работает...
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
h.m.f.
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #4 : 27-12-2010 07:57 » 

Спасибо за каркас. Переписал свое приложение в соответствии с ним. В итоге все равно получил зависание (1 раз на 10 остановов) на методе thread.Join().
Проблему удалось решить заменой thread.Join() на thread.Join(500). Только вот почему добавление таймаута решает проблему понять не могу.
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #5 : 27-12-2010 10:23 » 

h.m.f., возможно в этих случаях происходит попытка несколько раз вызвать Begin до End. Для защиты от этого, естественно, нужно несколько усложнить решение:
Код: (C#)
        public void Begin()
        {
            if(!this.running)
            {
                this.thread = new Thread(new ThreadStart(this.Run));
                this.running = true;
                this.thread.Start();
            }
        }

        public void End()
        {
            if(this.running)
            {
                this.running = false;
                this.thread.Join();
                this.thread = null;
            }
        }
Join с таймаутом не решает проблемы - поток остаётся зависшим, и по мере работы приложения число таких потоков-зомби будет возрастать, будут утечки памяти и падение производительности.

Возможно также, что внутренний цикл потока на чём-то зависает, и итерация не проходит. Грубым решением будет вместо Join вызвать у потока Abort, при этом внутри потока будет возникать исключительная ситуация, и если там нет обработчиков исключительных ситуаций, то поток будет принудительно прерван.
« Последнее редактирование: 27-12-2010 10:26 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
h.m.f.
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #6 : 27-12-2010 13:27 » 

Получил работающий код. Поток зависал в методе обновления lable'а. Устранил зависание добавив проверку условия того, что фоновый поток все еще работает Scalper2.IsRunning:
Код:
void Scalper2_SummarizeInfoChanged(object sender, SimpleScalper2.SummarizeInformationEventHandler e)
        {
           //throw new NotImplementedException();                     
            if (Dispatcher.Thread != Thread.CurrentThread)
            {
                if (!lblTransactionCount.Dispatcher.CheckAccess() && Scalper2.IsRunning)
                {
                    lblTransactionCount.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                        new Action(
                            delegate()
                            {
                                lblTransactionCount.Content = e.SummInfo.TransactionCount.ToString();
                            }
                    ));
                }
            }
        }
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #7 : 27-12-2010 15:15 » 

h.m.f., нехорошо, когда два потока обрабатывают одни и те же элементы управления без синхронизации. Зависания могут происходить из-за этого. Надо:
Код: (C#)
lock(lable)
{
    lable.Content = x.ToString();
}
И во втором потоке также проводить операции в критической секции.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
h.m.f.
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #8 : 28-12-2010 14:25 » 

Dimka, объясни где, что и чем залочить, чтобы корректно работал код ниже (на форме 2 кнопки, по нажатии на одной запускается код в потоке и выключается эта кнопка, при нажатии на второй - поток останавливается и выключается кнопка выключения)
Код:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;

namespace WpfApplication1
{
    /// <summary>
    /// Логика взаимодействия для Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        Scalper scalper;
        Thread thread;
        Button btnStart;
        Button btnStop;


        public Window1()
        {
            InitializeComponent();
           
            btnStart = new Button();
            btnStart.Content = "Start";           
            btnStart.HorizontalAlignment = HorizontalAlignment.Left;
            btnStart.Width = 100;
            btnStart.Height = 25;
            btnStart.Click += new RoutedEventHandler(btnStart_Click);
            this.MyGrid.Children.Add(btnStart);

            btnStop = new Button();
            btnStop.Content = "Stop";
            btnStop.HorizontalAlignment = HorizontalAlignment.Right;
            btnStop.Width = 100;
            btnStop.Height = 25;
            btnStop.Click += new RoutedEventHandler(btnStop_Click);
            this.MyGrid.Children.Add(btnStop);         
        }     
             

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            scalper = new Scalper();
            scalper.RobotStateChanged += new EventHandler<Scalper.IsRunningEventHandler>(scalper_RobotStateChanged);           

            this.thread = new Thread(new ThreadStart(scalper.Run));
            this.thread.Start();   
        }

        void scalper_RobotStateChanged(object sender, Scalper.IsRunningEventHandler e)
        {
            //throw new NotImplementedException();

            bool EnableStartButton;
            if (e.IsRunning)
            {
                EnableStartButton = false;
            }
            else
            {
                EnableStartButton = true;
            }

            if (Dispatcher.Thread != Thread.CurrentThread)// && Scalper.DoWork)
            {               
                if (!btnStart.Dispatcher.CheckAccess())
                {
                    btnStart.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                     new Action(
                         delegate()
                         {
                             btnStart.IsEnabled = EnableStartButton;
                         }
                 ));
                }               

                if (!btnStop.Dispatcher.CheckAccess())
                {
                    btnStop.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                     new Action(
                         delegate()
                         {

                             btnStop.IsEnabled = !EnableStartButton;
                         }

                 ));
                }               
            }
            else
            {
                btnStart.IsEnabled = EnableStartButton;               
                btnStop.IsEnabled = !EnableStartButton;
            }
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            scalper.Stop();
            this.thread.Join();
            this.thread = null;
        }                 
    }

    public class Scalper
    {       
        public class IsRunningEventHandler : EventArgs
        {
            private bool isRunning;
            public bool IsRunning
            {
                get { return this.isRunning; }
            }
            public IsRunningEventHandler(bool isRunning)
                : base()
            {
                this.isRunning = isRunning;
            }
        }
       
        public event EventHandler<IsRunningEventHandler> RobotStateChanged;
       
        private void OnRobotStateChanged()
        {
            if (this.RobotStateChanged != null)
            {
                this.RobotStateChanged(this, new IsRunningEventHandler(this.doWork));
            }
        }
       
        private bool doWork;
        public bool DoWork
        {
            get { return doWork; }
            set
            {
                doWork = value;
                OnRobotStateChanged();
            }
        }
                             
        public void TestWorkInThread()
        {           
            for (int i = 0; i < 500; i++)
            {
                if (doWork)
                {                 
                    Thread.Sleep(1000);
                }
                else
                {
                    return;
                }
            }
        }

        public void Run()
        {
            this.DoWork = true;

            TestWorkInThread();

            this.DoWork = false;
        }

        public void Stop()
        {
            this.DoWork = false;
        }
    }
}

Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #9 : 28-12-2010 21:30 » 

h.m.f., вот давно бы так Улыбаюсь

Самое правильное и очевидное: "включать"/"выключать" кнопки в методах btnStart_Click и btnStop_Click. Это будет происходить в одном потоке, поэтому блокировать ничего не придётся.

А вот реализацию scalper_RobotStateChanged я не понимаю. Зачем вот это вот:
Код: (C#)
                if (!btnStart.Dispatcher.CheckAccess())
                {
                    btnStart.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                     new Action(
                         delegate()
                         {
                             btnStart.IsEnabled = EnableStartButton;
                         }
                 ));
                }    
Ты можешь объяснить, как это работает?
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
h.m.f.
Участник

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 29-12-2010 07:04 » 

Пример написан с целью разобраться в теме (создание потокобезопасного кода или как он там называется). Поэтому сейчас в большей степени интересует как корректно закодить обращение к контролам из разных потоков.

По поводу реализации scalper_RobotStateChanged не совсем понимаю, что именно смущает. Вроде использовал шаблоны с мсдн:
Dispatcher.CheckAccess тут: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.checkaccess(VS.95).aspx
Dispatcher.Invoke тут: http://msdn.microsoft.com/ru-ru/library/system.windows.threading.dispatcher.invoke.aspx
Action тут: http://msdn.microsoft.com/en-us/library/system.action(v=VS.90).aspx

Происходит следующее (в моем понимании): определяется может ли поток использовать контрол; если да, то вызывается делегат обновления этого контрола от имени основного потока (в моем случае того, в котором отрисовывается главное окно)
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #11 : 29-12-2010 12:57 » 

Код: (C#)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace wpfTest
{
    public partial class MainWindow : Window
    {
        class Process
        {
            public class ProcessEventArgs : EventArgs
            {
                private ulong counter;

                public ProcessEventArgs(ulong counter)
                {
                    this.counter = counter;
                }

                public ulong Counter
                {
                    get
                    {
                        return this.counter;
                    }
                }
            }

            private ulong counter;
            private Thread thread;
            private bool running;

            public Process()
            {
                this.counter = 0;
                this.thread = null;
                this.running = false;
            }

            public event EventHandler AutoFinished;
            public event EventHandler<ProcessEventArgs> Updated;

            public void Begin()
            {
                if (!this.running)
                {
                    this.counter = 0;
                    this.thread = new Thread(new ThreadStart(this.run));
                    this.running = true;
                    this.thread.Start();
                }
            }

            public void End()
            {
                if (this.running)
                {
                    this.running = false;
                    this.thread.Join();
                }
            }

            private void run()
            {
                DateTime startTime = DateTime.Now;
                TimeSpan timeout = new TimeSpan();
                while (running && timeout.TotalSeconds < 10)
                {
                    this.counter += 1;
                    timeout = DateTime.Now - startTime;
                    this.OnUpdated();
                    Thread.Sleep(100);
                }
                if (running)
                {
                    this.running = false;
                    this.OnAutoFinished();
                }
            }

            private void OnAutoFinished()
            {
                if (this.AutoFinished != null)
                {
                    this.AutoFinished(this, new EventArgs());
                }
            }

            private void OnUpdated()
            {
                if (this.Updated != null)
                {
                    this.Updated(this, new ProcessEventArgs(this.counter));
                }
            }
        }

        private Process process;

        public MainWindow()
        {
            InitializeComponent();
            this.StopButton.IsEnabled = false;
            this.process = new Process();
            this.process.Updated += new EventHandler<Process.ProcessEventArgs>(process_Updated);
            this.process.AutoFinished += new EventHandler(process_AutoFinished);
        }

        void process_AutoFinished(object sender, EventArgs e)
        {
            Delegate action = new Action(
                delegate()
                {
                    this.Stop();
                }
            );
            this.Dispatcher.BeginInvoke(action);
        }

        void process_Updated(object sender, MainWindow.Process.ProcessEventArgs e)
        {
            Delegate action = new Action<ulong>(
                delegate(ulong counter)
                {
                    this.InfoLabel.Content = counter.ToString();
                }
            );
            this.Dispatcher.BeginInvoke(action, e.Counter);
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            this.Start();
            this.process.Begin();
        }

        private void StopButton_Click(object sender, RoutedEventArgs e)
        {
            this.Stop();
            this.process.End();
        }

        private void Start()
        {
            this.StartButton.IsEnabled = false;
            this.StopButton.IsEnabled = true;
        }

        private void Stop()
        {
            this.StartButton.IsEnabled = true;
            this.StopButton.IsEnabled = false;
        }
    }
}
Во-первых, здесь нет ненужных проверок, поскольку точно известно, из какого потока какой обработчик вызывается. Во-вторых, поручение из одного потока другому выполнить какое-то действие происходит асинхронно. Это важно, поскольку поток окна может вызвать Join и спать, ожидая завершения вспомогательного потока, а вспомогательный поток в обработчике пытается через Invoke заставить поток окна выполнить действие и ожидает, когда оно будет выполнено. Происходит deadlock. Для преодоления этой ситуации нужно не ожидать выполнения действия потоком окна - использовать BeginInvoke. Но при этом вспомогательный поток, не синхронизируясь с окном, начинает работать с полной производительностью, что может привести к забиванию очереди действий диспетчера окна и зависанию окна с точки зрения пользователя. Для этого нужно снизить интенсивность событий во вспомогательном потоке - у меня это делается при помощи Sleep, но можно и другими способами.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #12 : 29-12-2010 16:43 » new

Чтобы не тормозить вспомогательный поток, но при этом не "зафлуживать" основной поток обновлениями, можно написать вспомогательный класс "сторожа", который не позволяет генерировать событие чаще, чем 1 раз в течение заданного периода.

Например:
Код: (C#)
            class FrequencyControl
            {
                private long period;
                private DateTime previousAction;

                public FrequencyControl(long period)
                {
                    this.period = period;
                    this.previousAction = DateTime.MinValue;
                }

                public bool TryAction()
                {
                    DateTime currentAction = DateTime.Now;
                    bool result = (currentAction - this.previousAction).TotalMilliseconds >= this.period;
                    if (result)
                    {
                        this.previousAction = currentAction;
                    }
                    return result;
                }
            }

Объект этого класса создаётся с параметром "период" (в миллисекундах). Пользователь при желании что-то сделать вызывает у объекта метод TryAction, и если этот вызов происходит слишком часто (чаще "периода"), то метод возвращает false. По истечении периода или при более редких вызовах метод возвращает true, тем самым резрашая пользователю выполнить его действие.

Тогда Sleep из основного цикла можно убрать, а генератор события расширить:
Код: (C#)
            private void OnUpdated()
            {
                if (this.Updated != null && this.frequencyControl.TryAction())
                {
                    this.Updated(this, new ProcessEventArgs(this.counter));
                }
            }

Для GUI это нормально - нет смысла обновлять на экране надписи чаще, чем способен заметить человеческий глаз.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines