none
Как засинхронизировать выполнение функций в многопоточном приложении? RRS feed

  • Вопрос

  • Здравствуйте.

    У меня в приложении WPF один главный тред UI (окно приложения), в классе которого определён DispatcherTimer,

    private DispatcherTimer candleDrawingTimer;

    Из треда UI, по кнопке, я запускаю второй тред. В этом втором треде объявляю библиотечный COM-объект создаю его и подписываю на обработчик. Привожу этот код

    CP2DataStream FuturesInfoStream = new CP2DataStreamClass();
    FuturesInfoStream.StreamDataEnd += new IP2DataStreamEvents_StreamDataEndEventHandler(FuturesInfoStream_StreamDataEnd);

    Обработчик работает в контексте треда, в котором определён COM-объект, это я узнавал. Затем, из этого обработчика асинхронно вызываю функцию для отрисовки графика на экране.

    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new DrawCandleDelegate(DrawCandle));

    Раз асинхронно, то получается, что она выполняется в своём треде. Т.е. создался ещё один тред. Асинхронно - из за того, что пока она рисует очередную часть графика, другие обработчики COM-объекта (ещё два) могли набирать в буфер новые данные, для следующей порции отрисовки. Теперь. Во время выполнения асинхронно вызванной функции DrawCandle() тикнул CandleDrawingTimer (определённый в треде UI), сигнализирующий о том, что время для сбора данных для отрисовки очередной свечи на графике истекло и следует выполнить кое-какие действия (в нём же - в обработчике таймера). Но. Эти действия можно выполнить только тогда, когда асинхронно вызванная DrawCandle() закончит своё выполнение. Для этого, я объявил в классе окна (у меня приложение WPF) булевый флаг

    private Boolean f_DrawCandle = false;

    который устанавливается в true перед вызовом DrawCandle(), а сбрасывается уже в самой DrawCandle() в её последней строке перед выходом из неё. И у меня обработчик таймера, перед тем как выполнить требуемые действия крутит следующий цикл while

    //Ждать, пока выполняется DrawCandle
    while (f_DrawCandle == true) { ;}

    А после уже выполняет некоторые действия. Как вы понимаете, таймер может тикнуть как во время выполнения DrawCandle, так и в то время, когда она не выполняется. Синхронизация нужна на тот случай, когда он тикнет при выполнении DrawCandle(). По моему, синхронизировать через доморощенный флаг выполнение обработчика таймера из треда окна приложения и работу функции из другого треда - неудачное решение. Подскажите, пожалуйста как это можно сделать средствами NET Framework 4.0. Ведь есть же наверное какие-нибудь специальные средства для этого - что бы функция, запущенная в одном треде ожидала завершения функции запущенной в другом треде.

    С уважением Евгений.

    28 июня 2012 г. 18:38

Ответы

  • Вот выдержка из описания DispatcherTimer:

    "Выполнение таймеров не гарантируется в точности в тот момент, когда истекает заданный интервал времени, но они гарантировано не выполняются до истечения этого интервала. Причина этого в том, что операции DispatcherTimer помещаются в очередь Dispatcher подобно любым другим операциям. Когда будет выполнена операция DispatcherTimer, зависит от других заданий в очереди и их приоритетов."

    Ответил на ваш вопрос?

    • Помечено в качестве ответа TownSparrow 30 июня 2012 г. 15:20
    30 июня 2012 г. 12:39
    Отвечающий

Все ответы

  • Добрый день.

    Не знаю, разочарую вас или обрадую, но у вас в приложении всего два потока. Первый - UI, второй, тот в котором работает COM-объект. Диспетчер главного потока получает задания из двух мест, из:

    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new DrawCandleDelegate(DrawCandle));

    и из вашего

    private DispatcherTimer candleDrawingTimer;

    Они ставятся в его очередь, по мере окончания одного задания, запускается в этом же потоке другое задание. Так что не волнуйтесь, гонок у вас не будет.

    Про объекты синхронизации можете почитать здесь или на MSDN.

    • Предложено в качестве ответа Abolmasov Dmitry 29 июня 2012 г. 6:20
    • Помечено в качестве ответа TownSparrow 30 июня 2012 г. 11:23
    • Снята пометка об ответе TownSparrow 30 июня 2012 г. 11:31
    • Отменено предложение в качестве ответа TownSparrow 30 июня 2012 г. 11:32
    • Предложено в качестве ответа PashaPash 30 июня 2012 г. 12:58
    28 июня 2012 г. 18:50
    Отвечающий
  • Спасибо, Алексей. Извиняюсь за задержку с ответом. Меня вы разумеется обрадовали. Т.е. получается, что строку

    while (f_DrawCandle == true) { ; }

    я могу смело убрать из кода обработчика срабатывания таймера - из тех мест в нём, где мне необходимо дождаться окончания выполнения DrawCandle(). Т.к. посколько и обработчик таймера и DrawCandle() диспетчер ставит в одну очередь на выполнение, то если таймер "тикнул" во время выполнения DrawCandle(), то код обработчика срабатывания таймера (начиная с его самой первой строки) начнёт выполняться только после окончания выполнения кода DrawCandle() ? Это упрощает. Это хорошо. Рекомендованные вами статьи я тоже прочитал - полезно.

    Но у меня есть ещё один вопрос. У меня в программе есть ещё один объект типа DispatcherTimer (т. е, как из этого следует - таких таймеров у меня в программе два). Так вот я хочу уточнить. если этот второй таймер "тикнул" уже после того как "тикнул" первый таймер, то обработчик этого второго таймера будет ждать окончания выполнения обработчика первого таймера, даже в том случае, если обработчик первого таймера, в свою очередь, тоже ждёт окончания выполнения DrawCandle()? Т.е. диспетчер поставит в одну очередь на выполнение и DrawCandle() и обработчик первого таймера и обработчик второго таймера и вероятность гонки будет исключена? У меня в коде эти два таймера запускаются последовательно друг за другом:

    вот таким образом.

    // Если не запущен таймер разметки:
    if (!timeAxisQuantumsTimer.IsEnabled)
    {
        // Запускаем таймер разметки
        // и фиксируем дату и время начала торговых операций.
        timeAxisQuantumsTimer.Start();
        layoutControl.ExchangeTradeStartTime = DateTime.Now;
    }
    // Если не запущен таймер отрисовки свечи:
    if (!candleDrawingTimer.IsEnabled)
    {
        // Запускаем таймер отрисовки свечи
        // и разрешаем сбор данных для построения первой свечи.
        candleDrawingTimer.Start();
        f_StartCandleDataAcquisition = true;
    }

    С уважением  Евгений.





    • Изменено TownSparrow 30 июня 2012 г. 12:31
    30 июня 2012 г. 12:18
  • Вот выдержка из описания DispatcherTimer:

    "Выполнение таймеров не гарантируется в точности в тот момент, когда истекает заданный интервал времени, но они гарантировано не выполняются до истечения этого интервала. Причина этого в том, что операции DispatcherTimer помещаются в очередь Dispatcher подобно любым другим операциям. Когда будет выполнена операция DispatcherTimer, зависит от других заданий в очереди и их приоритетов."

    Ответил на ваш вопрос?

    • Помечено в качестве ответа TownSparrow 30 июня 2012 г. 15:20
    30 июня 2012 г. 12:39
    Отвечающий
  • Т.е. получается, что операция DispatcherTimer'а всё равно не прервёт ту операцию из очереди Dispatcher'а, которая уже выполняется (начала выполняться раньше операции таймера) и имеет одинаковый приоритет с операцией таймера (предположим DispatcherPriority.Normal). Я нигде не указываю явно приоритет, кроме как в асинхронных вызовах функций, изменяющих UI, выполняемых из треда, порождённого по кнопке из UI. Там я указываю DispatcherPriority.Normal. Т.е. получается, что приоритет у всех операций в очереди Dispatcher'а одинаковый. Ведь по умолчанию по-моему, приоритет - DispatcherPriority.Normal?

    30 июня 2012 г. 13:22
  • Приоритет влияет когда задачи выбираются из очереди на выполнение. Выполняющуюся задачу, даже при поступлении задачии с более высоким приоритетом, "приостанавливать" никто не будет...
    Отвечающий