none
Многопоточность: задержка перед запуском или приостановка во время выполнения?

    Вопрос

  • Ситуация следующая:

    В цикле FOR запускаю 3 потока.

    Каждый поток должен анализироваться на выбранное значение из таблицы SQL и при верном значении выполняться/запускаться.

    Все 3 потока должны выполняться одновременно и если какой то поток имеет выбранное значение из SQL = 0 то он не должен выполняться/запускаться пока значение не будет 1, остальные потоки не зависят от состояния задержанного/не запущенного потока и должны выполняться.

    Подскажите пожалуйста как лучше это реализовать, язык VB?


    19 сентября 2013 г. 8:58

Ответы

  • Итак пример под ваш вопрос.

    Для эмуляции данных считываемых из базы данных, я буду пользоваться вот таким классом:

    class MyThread
    {
        public string Name { get; set; }
    
        public bool IsReady { get; set; }
    }

    Для эмуляции базы данных классом:

    class DbEmulator
    {
        int _readCount = 0;
    
        List<MyThread> _data = null;
    
        public DbEmulator()
        {
            _readCount = 0;
            _data = new List<MyThread>();
            _data.Add(new MyThread() { Name = "A", IsReady = false });
            _data.Add(new MyThread() { Name = "B", IsReady = false });
            _data.Add(new MyThread() { Name = "C", IsReady = false });
        }
    
        public IEnumerable<MyThread> Read()
        {
            if (_readCount < 3)
            {
                _data[_readCount].IsReady = true;
                _readCount++;
            }
            return _data;
        }
    }

    Для хранения информации о запущенных потоках:

    class ThreadInfo
    {
        public Thread Thread { get; set; }
    
        public AutoResetEvent StartEvent { get; set; }
    
        public bool IsStarted { get; set; }
    }

    Собственно деонстрация. При запуске программы, считываем имеющиеся данные из БД и создаем по потоку на каждую считанную единицу данных, также, для реализации ожидания создаем AutoResetEvent и в зависимости от считанного свойства IsReady ставим ему статус. Ну и запускаем отдельный поток, который будет раз в пять секунд считывать данные из БД и информировать нас о том, что он там считал. Если некий флаг IsReady меняется на true, то вызываем на соответствующем AutoResetEvent-е метод Set:

    class Program
    {
        static void Main(string[] args)
        {
            List<ThreadInfo> threads = new List<ThreadInfo>();
            DbEmulator context = new DbEmulator();
            var data = context.Read(); // Первое чтение, только один в статусе IsReady
            foreach (var item in data)
            {
                AutoResetEvent autoResetEvent = new AutoResetEvent(item.IsReady);
                string name = item.Name;
                ThreadInfo threadInfo = new ThreadInfo()
                {                    
                    Thread = new Thread(new ThreadStart(() => Run(Work, name, autoResetEvent))),
                    StartEvent = autoResetEvent,
                    IsStarted = item.IsReady
                };
                threadInfo.Thread.Start(); // Запускаем поток
                threads.Add(threadInfo);
            }
            // Запускаем поток, для последовательного чтения данных
            Thread checker = new Thread(new ThreadStart(() =>
                {
                    while (threads.Any(t => !t.IsStarted))
                    {
                        Thread.Sleep(5000);
                        var newData = context.Read();
                        Console.Write("Статусы: ");
                        foreach (var d in newData)
                        {
                            Console.Write("{0} ", d.IsReady);
                        }
                        Console.WriteLine();
                        // Проверка статусов
                        for (int i = 0; i < newData.Count(); i++)
                        {
                            lock (threads[i])
                            {
                                if (newData.ElementAt(i).IsReady != threads[i].IsStarted && newData.ElementAt(i).IsReady)
                                {
                                    // Ок, пора запускать очередной поток
                                    threads[i].StartEvent.Set();
                                    threads[i].IsStarted = true;
                                }
                            }
                        } 
                    }
                }));
            checker.Start();
            Console.ReadKey();
        }
    
        static void Run(Action<string> p_method, string p_param, AutoResetEvent p_startEvent)
        {
            p_startEvent.WaitOne();
            p_method(p_param);
        }
    
        static void Work(string p_name)
        {
            Console.WriteLine(p_name);
        }
    }

    Как это выглядит после запуска:

    Через 10 секунд:

    Ну и ссылка на чуть более подробное описание. По ссылке есть и второй способ решения поставленной задачи, вот только в нем я идею запуска немного переиначил. Там потоки создаются только по мере изменения данных в БД.

    19 сентября 2013 г. 16:24
    Отвечающий
  • У вас ссылка ведет на эту же тему. Но, да, можно использовать и такой механизм. У него есть как свои плюсы, так и минусы. Если ожидание будет небольшим, то в связи с требованиями применяемыми к уведомлениям, может оказаться эффективней предложенная мной схема, если интервалы ожидания изменения поля IsReady долгие, то схема с уведомлениями может оказаться эффективнее. Кстати, Petalvik, при использовании уведомлений, коннекшен к базе держится открытым? А то я с ходу не нашел как оно там работает, а тестовый сценарий писать лениво ;)

    20 сентября 2013 г. 5:49
    Отвечающий
  • при использовании уведомлений, коннекшен к базе держится открытым?

    Да, пока подписка на уведомления остаётся в силе, соединение держится открытым.
    20 сентября 2013 г. 11:50

Все ответы

  • "Каждый поток должен анализироваться на выбранное значение из таблицы SQL и при верном значении выполняться/запускаться." - не совсем понятно. Перефразируйте свой вопрос.

    Сделаем содержимое сообщества лучше, вместе!

    19 сентября 2013 г. 9:01
    Модератор
  • Добрый день.

    Да, лучше уточните вопрос. Но насколько я понял, вам нужно синхронизировать потоки, пока не будет достигнуто некоторое значение. Создаете три AutoResetEvent, по одному на каждый поток. В начале вызываете Wait на соответствующем эвенте. Как только у вас загрузились данные и там не ноль, то вызываете на нужном эвенте Set, поток его ожидающий перейдет к следующей команде после Wait. Ну а для тех у кого считался 0, ждете когда подготовятся данные и после этого вызываете на их эвентах Set.

    P.s. На VB не пишу, поэтому могу помочь только с описанием алгоритмов и подсказкой какие классы использовать.

    19 сентября 2013 г. 9:27
    Отвечающий
  • Есть 3 потока, которые запускаются параллельно

    Есть таблица SQL, в которой есть 2 поля:1. имя потока и 2. булево значение

    после запуска всех трех потоков, по каждому потоку делается запрос к этой таблице и проверяется булево значение. Если оно не 0, то данный поток должен приостанавливаться до тех пор пока в данной таблице по этому потоку переменная не будет равна 0.

    19 сентября 2013 г. 9:28
  • В добавление к моему предыдущему ответу, в свете вашего дополнения.

    Создайте четвертый поток, который будет раз в несколько секунд лазить в БД и смотреть, не появились ли там единички. Появились, вызывать Set.

    19 сентября 2013 г. 9:30
    Отвечающий
  • дело в том, что количество потоков может быть разным.

    а можете на своем примере показать как Вы бы вызывали потоки и их анализировали?

    19 сентября 2013 г. 9:37
  • Если вас устроит, то на C# и вечером. На VB, к сожалению, не пишу...
    19 сентября 2013 г. 9:39
    Отвечающий
  • спасибо!
    19 сентября 2013 г. 9:40
  • Итак пример под ваш вопрос.

    Для эмуляции данных считываемых из базы данных, я буду пользоваться вот таким классом:

    class MyThread
    {
        public string Name { get; set; }
    
        public bool IsReady { get; set; }
    }

    Для эмуляции базы данных классом:

    class DbEmulator
    {
        int _readCount = 0;
    
        List<MyThread> _data = null;
    
        public DbEmulator()
        {
            _readCount = 0;
            _data = new List<MyThread>();
            _data.Add(new MyThread() { Name = "A", IsReady = false });
            _data.Add(new MyThread() { Name = "B", IsReady = false });
            _data.Add(new MyThread() { Name = "C", IsReady = false });
        }
    
        public IEnumerable<MyThread> Read()
        {
            if (_readCount < 3)
            {
                _data[_readCount].IsReady = true;
                _readCount++;
            }
            return _data;
        }
    }

    Для хранения информации о запущенных потоках:

    class ThreadInfo
    {
        public Thread Thread { get; set; }
    
        public AutoResetEvent StartEvent { get; set; }
    
        public bool IsStarted { get; set; }
    }

    Собственно деонстрация. При запуске программы, считываем имеющиеся данные из БД и создаем по потоку на каждую считанную единицу данных, также, для реализации ожидания создаем AutoResetEvent и в зависимости от считанного свойства IsReady ставим ему статус. Ну и запускаем отдельный поток, который будет раз в пять секунд считывать данные из БД и информировать нас о том, что он там считал. Если некий флаг IsReady меняется на true, то вызываем на соответствующем AutoResetEvent-е метод Set:

    class Program
    {
        static void Main(string[] args)
        {
            List<ThreadInfo> threads = new List<ThreadInfo>();
            DbEmulator context = new DbEmulator();
            var data = context.Read(); // Первое чтение, только один в статусе IsReady
            foreach (var item in data)
            {
                AutoResetEvent autoResetEvent = new AutoResetEvent(item.IsReady);
                string name = item.Name;
                ThreadInfo threadInfo = new ThreadInfo()
                {                    
                    Thread = new Thread(new ThreadStart(() => Run(Work, name, autoResetEvent))),
                    StartEvent = autoResetEvent,
                    IsStarted = item.IsReady
                };
                threadInfo.Thread.Start(); // Запускаем поток
                threads.Add(threadInfo);
            }
            // Запускаем поток, для последовательного чтения данных
            Thread checker = new Thread(new ThreadStart(() =>
                {
                    while (threads.Any(t => !t.IsStarted))
                    {
                        Thread.Sleep(5000);
                        var newData = context.Read();
                        Console.Write("Статусы: ");
                        foreach (var d in newData)
                        {
                            Console.Write("{0} ", d.IsReady);
                        }
                        Console.WriteLine();
                        // Проверка статусов
                        for (int i = 0; i < newData.Count(); i++)
                        {
                            lock (threads[i])
                            {
                                if (newData.ElementAt(i).IsReady != threads[i].IsStarted && newData.ElementAt(i).IsReady)
                                {
                                    // Ок, пора запускать очередной поток
                                    threads[i].StartEvent.Set();
                                    threads[i].IsStarted = true;
                                }
                            }
                        } 
                    }
                }));
            checker.Start();
            Console.ReadKey();
        }
    
        static void Run(Action<string> p_method, string p_param, AutoResetEvent p_startEvent)
        {
            p_startEvent.WaitOne();
            p_method(p_param);
        }
    
        static void Work(string p_name)
        {
            Console.WriteLine(p_name);
        }
    }

    Как это выглядит после запуска:

    Через 10 секунд:

    Ну и ссылка на чуть более подробное описание. По ссылке есть и второй способ решения поставленной задачи, вот только в нем я идею запуска немного переиначил. Там потоки создаются только по мере изменения данных в БД.

    19 сентября 2013 г. 16:24
    Отвечающий
  • Честно говоря, странный способ: использовать передачу данных через БД.

    Какая именно СУБД используется? Если это MS SQL Server, то вместо постоянного опроса БД (что неэффективно), можно использовать уведомления: СУБД сама будет посылать сообщения об изменении данных.


    • Изменено Petalvik 20 сентября 2013 г. 11:40 Поправил гиперссылку.
    19 сентября 2013 г. 17:02
  • У вас ссылка ведет на эту же тему. Но, да, можно использовать и такой механизм. У него есть как свои плюсы, так и минусы. Если ожидание будет небольшим, то в связи с требованиями применяемыми к уведомлениям, может оказаться эффективней предложенная мной схема, если интервалы ожидания изменения поля IsReady долгие, то схема с уведомлениями может оказаться эффективнее. Кстати, Petalvik, при использовании уведомлений, коннекшен к базе держится открытым? А то я с ходу не нашел как оно там работает, а тестовый сценарий писать лениво ;)

    20 сентября 2013 г. 5:49
    Отвечающий
  • при использовании уведомлений, коннекшен к базе держится открытым?

    Да, пока подписка на уведомления остаётся в силе, соединение держится открытым.
    20 сентября 2013 г. 11:50
  • правильно ли я понимаю алгоритм.

    Со среды vb.net посылает sql запрос к базе, а в самой базе делается задержка и когда происходит изменение в интересующем меня поле, то база отдает ответ, так?

    если так, то можно поподробнее как в sql это делается, желательно на примере...

    15 января 2014 г. 15:57
  • правильно ли я понимаю алгоритм.

    Со среды vb.net посылает sql запрос к базе, а в самой базе делается задержка и когда происходит изменение в интересующем меня поле, то база отдает ответ, так?

    Нет. Не правильно. Вы просто с некоторой периодичностью опрашиваете БД и проверяете не поменялись ли данные.
    15 января 2014 г. 17:46
    Отвечающий