Ошибка (?) в работе ссылки на QUEUE

Clarion, Clarion 7

Модератор: Дед Пахом

Правила форума
При написании вопроса или обсуждении проблемы, не забывайте указывать версию Clarion который Вы используете.
А так же пользуйтесь спец. тегами при вставке исходников!!!
Ответить
Гость

Сообщение Гость »

Clarion 5.5D Enterprise

Задача следующая: один поток запускает другой поток и передает ему адрес своего экземпляра queue, которая должна быть заполнена в вызываемом потоке (типичное применение - выборка из файла по условию).
Сама очередь описана глобально с атрибутом thread.

Проблема: если в вызывающем потоке просто использовать ссылку на очередь и передать ее в вызываемый поток, то при возврате очередь не заполнена; а вот если сначала сделать new(queue), то все OK.
Где ошибка ???

Делать new не хочется, тем более, что и без него должно работать.

Привожу полный текст программы (можно сразу компилировать).

Код: Выделить всё

!==============================================================================================================
           PROGRAM
           MAP
             StartFill     ! Процедура запуска и показа рез-тов заполнения очереди Q
             Fill(string)  ! Заполнение выполняется здесь
           .

App  APPLICATION('   Main'),AT(0,0,250,257),SYSTEM,MAX,RESIZE
       MENUBAR
         ITEM('Начать'),USE(?Start)
       END
     END

Q    queue,thread   ! Очередь для заполнения
s      string(10)
     .

QRefsQueue  queue   ! Глобальная очередь, используемая всеми потоками для определения, с каким экземпляром Q работаем
Thrd          short   ! Номер потока, в котором показывается Q
QRef          &Q      ! Ссылка на Q в данном потоке
            .

  CODE
  OPEN(App)
  ACCEPT
    CASE EVENT()
    OF EVENT:CloseDown
      break

    OF EVENT:Accepted
      case field()
      of ?Start
        stop('В App (thread=' & thread() & ') ' & address(Q))  ! Проверим адрес Q в основном потоке
        t#=START(StartFill)
  . . .

!----------------------------------------------------
StartFill     procedure
Win  WINDOW('   Кол-во записей в Q'),AT(0,0,104,201),FONT('MS Sans Serif',10,,,CHARSET:CYRILLIC),HVSCROLL, !
         SYSTEM,GRAY,DOUBLE,MDI
       BUTTON('Start'),AT(14,4,28,12),USE(?StartBtn),SKIP,FLAT
       BUTTON('Stop'),AT(61,4,28,12),USE(?StopBtn),SKIP,FLAT,DISABLE
       LIST,AT(4,20,96,174),USE(?List),VSCROLL,FONT('Courier New',10,,,CHARSET:CYRILLIC),COLOR(COLOR:Silver), !
           FORMAT('20L(2)_~Заголовок~C@s20@E(,0C0C0C0H,,)') !,FROM(Q)
     END

QR  &Q

  code
! QR &= Q  !!! Так НЕ работает (т.е. в проц-ре Fill заполняется другой экземпляр Q),
             ! хотя показывает все время один и тот же адрес Q во все потоках.
! QR &= address(Q)  !!! Так вообще выдает ошибку, хотя компилируется нормально и по документации должно работать
  QR &= new(Q)  !!! Так работает

  OPEN(Win)
  ?List{PROP:From}=QR
  ACCEPT
    CASE EVENT()
    OF EVENT:CloseDown
      break

    OF 500h   ! Сообщение из Fill о занесении новой записи
      Win{PROP:Text}='Кол-во записей: ' & records(QR) !(Q)

    OF 501h   ! Сообщение из Fill о завершении
      disable(?StopBtn); enable(?StartBtn)

    OF EVENT:Accepted
      case field()
      of ?StartBtn
        stop('В StartCount (thread=' & thread() & ') ' & address(QR)) ! Проверим адрес экземпляра Q в этом потоке
        QRefsQueue.Thrd=thread(); QRefsQueue.QRef &= QR
        add(QRefsQueue,QRefsQueue.Thrd)   ! Через эту запись передадим ссылку на экземпляр Q
        newthread#=START(Fill,,thread())
        enable(?StopBtn); disable(?StartBtn)

      of ?StopBtn
        POST(EVENT:CloseDown,,newthread#,1)  ! Остановим заполнение Q
        disable(?StopBtn); enable(?StartBtn)
      .
  . .

  dispose(QR)

!----------------------------------------------------
! Эта процедура имитирует заполнение очереди записями из файла.
! Для ускорения процесса используется не событие от Timer, а
! повторная посылка в accept своего события 499h

Fill     procedure(ParentThread)

HiddenWindow WINDOW(''),AT(0,-10,0,0),GRAY,MDI
     END

QR   &Q
cnt  long, thread  ! Счетчик шагов

  code
  QRefsQueue.Thrd=ParentThread
  get(QRefsQueue,QRefsQueue.Thrd)  ! Получим ссылку на Q, используемую в вызвавшем потоке
  if errorcode()=0
    QR &= QRefsQueue.QRef  ! Нельзя напрямую использовать QRefsQueue.QRef, т.к. это глобальная переменная
  else                     ! будет изменена при старте StartFill в др.потоке
    stop('Не передан пар-р')
  .

  ! здесь адрес должен быть таким же, как в вызвавшей StartFill
  stop('В Count (thread=' & thread() & ') ' & address(QR))

  cnt=0
  open(HiddenWindow)
  post(499h)  ! Начнем цикл. Будет прерван по EVENT:CloseDown, пришедшему
  accept      ! от вызвавшего потока (StartFill) или после занесения в Q заданного кол-ва записей
    case event()
    of 499h
      cnt +=1
      if cnt %1000 =0
        QR.s=all(chr(val('a')+cnt/1000-1), 10); add(QR)
        post(500h,,ParentThread,1)   ! Для показа в вызвавшем потоке процесса заполнения Q
      .
!     yield
      if records(QR)>=50
        break
      else
        post(499h)  ! Следующий шаг (по Timer слишком медленно)
    . .
  .
  close(HiddenWindow)

  QRefsQueue.Thrd=ParentThread
  get(QRefsQueue,QRefsQueue.Thrd)  ! Вновь получим ссылку на Q, используемую в вызвавшем потоке
  if errorcode()=0                 ! и удалим ее за ненадобностью
    delete(QRefsQueue)
  .
  post(501h,,ParentThread)  ! Сообщим в StartFill, что заполнение завершено
Bourkov Andrei bav@makler.ru
Написал: ClaList(2)
Гость

Сообщение Гость »

Привет!

1. Шредовые переменные переинициализируются в каждои потоке. Т.е. по идее несмотря на совпадение адреса при обращении к шредовой переменной ты получаешь новый экземпляр (выполняется фоновое New)

2. QR &= address(Q) должно пропускаться компилятором, но работать НЕ должно.
Но должно работать QR &= (address(Q)), т.е. принудительная типизация по левой части выражения

3. Перед
QRefsQueue.Thrd=thread(); QRefsQueue.QRef &= QR
настоятельно рекомендуется делать Clear(QRefsQueue) (но на твой пример это не должно повлиять)

4. А вот тут у тебя будет утечка памяти

Код: Выделить всё

   QRefsQueue.Thrd=ParentThread
   get(QRefsQueue,QRefsQueue.Thrd)  ! Вновь получим ссылку на Q, используемую в вызвавшем потоке
   if errorcode()=0                 ! и удалим ее за ненадобностью
     delete(QRefsQueue)
   .
Перед Delete надо бы явно вызывать Dispose(QRefsQueue.QRef )

Но вот по существу кроме снятия атрибута шредовости с Q я ничего придумать не могу. К тому же при такой постановке задачи он действительно не нужен (ты же сам потоки диспетчеризуешь!), new и dispose (с поправками на вышеизложенное) спасут русскую демократию...

Александр Агеев (aageev@satren.ru)
Написал: ClaList(2)
Гость

Сообщение Гость »

Спасибо, Александр !

1. Это действительно так. И подтверждается это неправильной работой ссылки QR &= Q (Q, заполненная в проц-ре Fill, не та же, что в StartFill)

2. Не работает! После такого присвоения попытка обратиться QR.s='12345' завершается ошибкой (в отладчике это Access Violation).
Если посмотреть, какой адрес был получен после принудительной типизации - через address(QR), то он выглядит странным (я бы сказал, слишком большим).

4. У меня dispose выолняется в вызвавшем потоке (проц-ра StartFill) после закрытия его окна.
Но вот по существу кроме снятия атрибута шредовости с Q я ничего придумать не могу. К тому же при такой постановке задачи он действительно не нужен (ты же сам потоки диспетчеризуешь!), new и dispose (с поправками на >вышеизложенное) спасут русскую демократию...
К сожалению, мне нужна именно глобальная queue и именно с атрибутом thread: мой пример был лишь сильным упрощением реальной задачи. В ней используется несколько таких глобальных queue и они обрабатываются в разных процедурах, вызываемых из проц-ры Fill (переводится с CFD3.1 большой DOS-проект).

А проблема возникла из-за того, что процедуру фильтрации (Fill) пришлось делать в отдельном потоке. Она использует для фильтрации записей разные queue, которые подготовлены в вызывающем потоке (StartFill) и, значит, принадлежат ему.
Нужно было передать в вызываемый поток (Fill) ссылки на эти queue.
Но это не работает, если эти queue не были "созданы" через new.
А new использовать ОЧЕНЬ не хочется, т.к. нужно заменять имена этих queue и их полей во всех процедурах, где производится их подготовка (фактически это форма с заданием условий фильтрации).
И здесь новая проблема: нельзя будет в форме использовать в entry-контролах имена полей queue, которая передана по ссылке (естественно, придется все назначать через PROP:USE). А таких полей три десятка и на каждое навешаны операторы проверки ввода.

Короче говоря, в MDI-приложении глобальные queue с атрибутом thread и без использования new/dispose работают нормально, пока не требуется передавать ссылку на них в другой поток. Такая ссылка передается НЕВЕРНО, в том числе и с использованием группы-обертки, как это предлагал ранее Олег Руденко.

Может быть, Сергей Чушкин что-нибудь предложит, а то НУ ОЧЕНЬ НУЖНО.

P.S. Тестовый пример перенес в attachment.

Bourkov Andrei bav@makler.ru
Написал: ClaList(2)
Гость

Сообщение Гость »

Привет!

Вместо

Код: Выделить всё

Q    queue,thread   
s      string(10)
     .

QRefsQueue  group,thread 
Thrd          short   
QRef          &Q   
            .
QR  &Q,thread
Используй

Код: Выделить всё

Q    queue, pre(qq), type
s      string(10)
     .

QRefsQueue  QUEUE, pre(QRQ)
Thrd          short   
QRef          &Q   
            .
И будет тебе счастье. Только при закрытии последней процедуры потока не забудь убить её QRefsQueue.Qref

По фукнционалу будет то же, только при открытии потока надо будет самому находить/создавать её QRefsQueue.Thrd

Александр Агеев

(Добавление)

Александр, извини, ты не понял.

Мне нужна глобальная многопотоковая queue, которая сама по себе в нескольких потоках работает нормально.

Но как только я из одного потока пытаюсь передать ссылку на нее в другой поток, там эта ссылка не работает.

Вот еще раз конкретный (НЕработающий) пример. Не поленись проверить.
Я специально в нем в начале проц-ры StartFill закомментировал оператор new, который делает его работающим, но мне не подходит.

Код: Выделить всё

!==============================================================================================================
           PROGRAM
           MAP
             StartFill     ! Процедура запуска и показа рез-тов заполнения очереди Q
             Fill(string)  ! Заполнение выполняется здесь
           .

App  APPLICATION('   Main'),AT(0,0,250,257),SYSTEM,MAX,RESIZE
       MENUBAR
         ITEM('Начать'),USE(?Start)
       END
     END

Q    queue,thread   ! Очередь для заполнения
s      string(10)
     .

QRefsQueue  queue   ! Глобальная очередь, используемая всеми потоками для определения, с каким экземпляром Q работаем
Thrd          short   ! Номер потока, в котором показывается Q
QRef          &Q      ! Ссылка на Q в данном потоке
            .

  CODE
  OPEN(App)
  ACCEPT
    CASE EVENT()
    OF EVENT:CloseDown
      break

    OF EVENT:Accepted
      case field()
      of ?Start
        stop('В App (thread=' & thread() & ') ' & address(Q))  ! Проверим адрес Q в основном потоке
        t#=START(StartFill)
  . . .

!----------------------------------------------------
StartFill     PROCEDURE
Win  WINDOW('   Кол-во записей в Q'),AT(0,0,104,201),FONT('MS Sans Serif',10,,,CHARSET:CYRILLIC),HVSCROLL, !
         SYSTEM,GRAY,DOUBLE,MDI
       BUTTON('Start'),AT(14,4,28,12),USE(?StartBtn),SKIP,FLAT
       BUTTON('Stop'),AT(61,4,28,12),USE(?StopBtn),SKIP,FLAT,DISABLE
       LIST,AT(4,20,96,174),USE(?List),VSCROLL,FONT('Courier New',10,,,CHARSET:CYRILLIC),COLOR(COLOR:Silver), !
           FORMAT('20L(2)_~Заголовок~C@s20@E(,0C0C0C0H,,)') !,FROM(Q)
     END

QR  &Q

  code
 QR &= Q  !!! Так НЕ работает (т.е. в проц-ре Fill заполняется другой экземпляр Q),
             ! хотя показывает все время один и тот же адрес Q во все потоках.
!  QR &= new(Q)  !!! Так работает

  OPEN(Win)
  ?List{PROP:From}=QR
  ACCEPT
    CASE EVENT()
    OF EVENT:CloseDown
      break

    OF 500h   ! Сообщение из Fill о занесении новой записи
      Win{PROP:Text}='Кол-во записей: ' & records(QR) !(Q)

    OF 501h   ! Сообщение из Fill о завершении
      disable(?StopBtn); enable(?StartBtn)

    OF EVENT:Accepted
      case field()
      of ?StartBtn
        stop('В StartCount (thread=' & thread() & ') ' & address(QR)) ! Проверим адрес экземпляра Q в этом потоке
        QRefsQueue.Thrd=thread()
        QRefsQueue.QRef &= QR    ! Здесь с тем же успехом можно задать &= Q
        add(QRefsQueue,QRefsQueue.Thrd)   ! Через эту запись передадим ссылку на экземпляр Q
        newthread#=START(Fill,,thread())
        enable(?StopBtn); disable(?StartBtn)

      of ?StopBtn
        POST(EVENT:CloseDown,,newthread#,1)  ! Остановим заполнение Q
        disable(?StopBtn); enable(?StartBtn)
      .
  . .

!  dispose(QR)

!----------------------------------------------------
! Эта процедура имитирует заполнение очереди записями из файла.
! Для ускорения процесса используется не событие от Timer, а
! повторная посылка в accept своего события 499h

Fill     procedure(ParentThread)

HiddenWindow WINDOW(''),AT(0,-10,0,0),GRAY,MDI
     END

QR   &Q
cnt  long, thread  ! Счетчик шагов

  code
  QRefsQueue.Thrd=ParentThread
  get(QRefsQueue,QRefsQueue.Thrd)  ! Получим ссылку на Q, используемую в вызвавшем потоке
  if errorcode()=0
    QR &= QRefsQueue.QRef  ! Нельзя напрямую использовать QRefsQueue.QRef, т.к. это глобальная переменная
  else                     ! будет изменена при старте StartFill в др.потоке
    stop('Не передан пар-р')
  .

  ! здесь адрес должен быть таким же, как в вызвавшей StartFill
  stop('В Count (thread=' & thread() & ') ' & address(QR))

  cnt=0
  open(HiddenWindow)
  post(499h)  ! Начнем цикл. Будет прерван по EVENT:CloseDown, пришедшему
  accept      ! от вызвавшего потока (StartFill) или после занесения в Q заданного кол-ва записей
    case event()
    of 499h
      cnt +=1
      if cnt %1000 =0
        QR.s=all(chr(val('a')+cnt/1000-1), 10); add(QR)
        post(500h,,ParentThread,1)   ! Для показа в вызвавшем потоке процесса заполнения Q
      .
!     yield
      if records(QR)>=50
        break
      else
        post(499h)  ! Следующий шаг (по Timer слишком медленно)
    . .
  .
  close(HiddenWindow)

  QRefsQueue.Thrd=ParentThread
  get(QRefsQueue,QRefsQueue.Thrd)  ! Вновь получим ссылку на Q, используемую в вызвавшем потоке
  if errorcode()=0                 ! и удалим ее за ненадобностью
    delete(QRefsQueue)
  .
  post(501h,,ParentThread)  ! Сообщим в StartFill, что заполнение завершено
Bourkov Andrei
Написал: ClaList(2)
Гость

Сообщение Гость »

Может быть, Сергей Чушкин что-нибудь предложит, а то НУ ОЧЕНЬ НУЖНО.
Ничего на ум с ходу не приходит, кроме как привести выдержку их хэлпа для C6...

ADDRESS of a threaded variable
Before taking the ADDRESS() of a threaded variable would always return the same result regardless of what thread is running. In Clarion 6 every thread has its own memory location for a threaded variable. So ADDRESS() no longer will return the same value. If you need a constant address for a threaded global variable you can use the new function INSTANCE() and pass a Thread Number of 0. For example, to get a unique identifier for a threaded FILE that you can be certain will be the same regardless of what thread you are on you used to do ADDRESS(MyFile). You now do INSTANCE(MyFile, 0)

и ещё кусочек:

INSTANCE can be used instead of the ADDRESS( ) statement when ADDRESS( ) is not valid or available (e.g. FILE and QUEUE structures). ADDRESS(QUEUE) is a legal call, but it returns the address of the queue’s internal buffer. On the other hand, INSTANCE(QUEUE,THREAD()) returns the address of the queue’s internal structure.
For example, given the following QUEUE declarations:

SomeQueue QUEUE
...
END
QueueRef &QUEUE

The following assignment is correct:
QueueRef &= INSTANCE (SomeQueue, Somethread)
while the assignment shown below is not correct:
QueueRef &= ADDRESS (SomeQueue)
and sets the QueueRef variable to the wrong value.
INSTANCE is also valuable when you need the thread independent ID of the variable.

Сергей - chusha@mail333.com ; chusha@hotbox.ru

похожие задачи у меня имеют место быть, но только чуть по другому есть класс в котором есть ссылка на очередь, инициализируется в конструкторе либо черех &= NEW либо &= на глобальную Queue, далее я передаю по START адрес данного класса и нормально работаю с этим объектом, в частности с Queue данного объекта, может обрамить queue классом?

Andrew Myalin
andrew@arsis.ru
http://mavcla.arsis.ru (MAV Direct ODBC)
ICQ: 10659412
Yahoo group: clarion@yahoogroups.com
Написал: ClaList(2)
Гость

Сообщение Гость »

QR &= Instance(Q,Thread()) ! А вот так будет работать

После выхода первых релизов C60 я уже неоднократно писал, что трейдовая модель изменилась КАРДИНАЛЬНО!
О чем, собственно, не устают напоминать и сами SV.
Поэтому те приемы, которые я предлагал для прежних версий (C50/C55) в С60 не будут работать - надо использовать или новые средства самого языка или немного менять "обходной" код.
Но, что немаловажно, обход практически всегда есть!

=============================
С уважением, Олег А. Руденко.
Oleg_Rudenko@mail.ru
Oleg_Rudenko@mail333.com
Библиотека DynaLib
http://dynalib.narod.ru
Написал: ClaList(2)
Гость

Сообщение Гость »

может обрамить queue классом?
К сожалению, это тоже не получается (по крайней мере, если я правильно понял идею).

Прилагаю тестовую программу, переделанную под использование класса.
В ней в начале проц-ры StartFill используется ссылка на глобальную Queue. Это не работает: т.е. очередь заполняется в Fill, но она не видна в вызвавшем потоке StartFill.
Любопытно, что адрес этой самой глобальной Queue в разных потоках одинаковый.

Двумя строками ниже закомментирован вариант с New, который работает (по мере заполнения очереди рез-ты отображаются), но опять-таки мне не подходит.

Но все равно спасибо.

Bourkov Andrei bav@makler.ru

А... нельзя дилетантский вопрос - что будет, если сделать ровно одну глобальную очередь, в которую добавить поле Thread? А потом при необходимости из неё выгребать нужные данные. Примерно с полгода назад слегка потрах... повозился с близкой задачей, переводя приложение с CW55 на CW6 (ну очень плохо треды работали). Примерно на таком варианте и остановился.

--
C уважением
Yuri
Адрес:yufil@mail.ru

(Добавление)

Спасибо Юрию и Олегу.
Но у меня 5.5, а не 6 версия.
Так что "обход", видимо, единственный и для меня сильно неудобный - это
использовать new.

Bourkov Andrei

Дык, речь не о том. Может быть, просто слегка упростить жизнь себе и набирать выборки в один общий список, а потом из него всё забирать...

Мне в своей задаче хотелось иметь раздельные списки (очереди) параметров, подразделяемых на локальные (из одного треда)/глобальные (для всех тредов)/параметры (передаются при вызове), постоянные (сохраняются между сеансами работы)/временные, общие (едины для всех одновременно работающих)/пользовательские (у каждого свои).

Так и не сумел корректно наладить правильную работу, в конце концов свалил в общую очередь и выставил флажки - тип переменной и список тредов, в которых она доступна.

Тем более, что есть потенциальная опасность, что вызывающая задача кончится раньше, чем вызываемая и Queue просто перестанет существовать.

--
C уважением
Yuri
Написал: ClaList(2)
Ответить