Experimental Physics and Industrial Control System
On 09/01/2017 06:31 PM, Andrew Johnson wrote:
> Hi Giacomo,
>
> Welcome to the EPICS community!
Hello Andrew, thanks for Your kind and thorough reply.
>
> On 09/01/2017 09:59 AM, Giacomo S. wrote:
>> I am new to Epics, and I am writing a C++ multi threaded library in
>> order to write GUIs in Qt and I have the following constraints:
> Are you aware that there are already a number of EPICS projects using
> Qt? You might want to take a look at them since they have already
> debugged the kinds of issues you may be faced with. The main one you
> should start with is epicsQt, https://github.com/qtepics or
> https://qtepics.github.io/index.html
I wasn't aware of the specific project, but I had read about epics and
Qt integration.
In this first step, I don't have to deal with Qt, just a plain C++ library.
>> - I want to monitor a pv in a separate thread;
>>
>> - when a new value is available, I want a callback to be invoked (always
>> in the same background thread). There I extract data and forward it to
>> the main (GUI) thread.
>>
>> - the background thread implements a wait condition so that it sleeps
>> until a new event arrives (for example, exit event or new variable to be
>> monitored is added)
> You should be aware that the Channel Access client library creates a
> background thread for each separate IOC it connects to, and it calls
> your callback routines from those threads. You have no control over
> that, if you talk to PVs on 2 IOCs you will get callbacks from 2
> threads.
That's a relevant information. Thus, monitoring 100 PVs means at least
100 threads in the application.
> The only difference that enabling preemptive callbacks has on
> that is to allow callbacks to be run as soon as they are ready to be
> run. For thread-sensitive systems like Qt you need to plan for that.
That's why enabling preemptive callbacks made things work as desired
without blocking within ca_pend_event()
Good.
>
>> I set things up like this in the init() method of a class I called
>> "CuMonitorActivity".
>>
>> I used a pv structure as in camonitor C example:
>>
>>
>> void CuMonitorActivity::init()
>> {
> <snip>
>> }
>>
>> NOTE: I enabled preemptive_callback so that I get
>> connection_handler_cb() called regularly when a new event arrives
> You didn't show the routine that actually creates the channel and passes
> in the connection_handler_cb() routine, but a CA *connection handler* is
> only called when the connection state changes, i.e. when the program
> first connects to the PV, and then again later if/when the IOC gets
> started or stopped (or the network is unplugged, etc); that callback
> doesn't see any data from the PV. If you want to see value changes from
> the PV you have to create a subscription to the channel, and give that a
> different callback routine.
Ok. Here's the snippet of code for the monitor setup:
void CuMonitorActivity::init()
{
CuData tk = getToken();
int nPvs = 1;
pv* pvs; /* Array of PV structures */
int result = ca_context_create(ca_enable_preemptive_callback);
if (result != ECA_NORMAL) {
// deal with error
}
else
{
pvs = (pv *) calloc (nPvs, sizeof(pv));
/* Connect channels */
memset(pvs[0].name, 0, 256);
strncpy(pvs[0].name, tk["src"].toString().c_str(), 255);
pvs[0].monitor_activity = this; // this is needed for my
internal stuff
/* Create CA connections */
int returncode = create_pvs(pvs, nPvs, connection_handler_cb);
if ( returncode ) {
// error
}
}
}
//
// taken from camonitor's "tool_lib.cpp"
// essentially calls ca_create_channel()
//
int create_pvs (pv* pvs, int nPvs, caCh *pCB)
{
int n;
int result;
int returncode = 0;
if (!tsInitC) /* Initialize start timestamp */
{
epicsTimeGetCurrent(&tsStart);
tsInitC = 1;
}
/* Issue channel connections */
for (n = 0; n < nPvs; n++) {
result = ca_create_channel (pvs[n].name,
pCB,
&pvs[n],
caPriority,
&pvs[n].ch_id);
if (result != ECA_NORMAL) {
fprintf(stderr, "CA error %s occurred while trying "
"to create channel '%s'.\n",
ca_message(result), pvs[n].name);
pvs[n].status = result;
returncode = 1;
}
}
return returncode;
}
Then, when connection_handler_cb is invoked:
void CuMonitorActivity::connection_handler_cb ( struct
connection_handler_args args )
{
// Extract the reference to this object
//
pv *ppv = ( pv * ) ca_puser ( args.chid );
CuMonitorActivity* cu_mona = ppv->monitor_activity;
// call the connection_handler of this object
cu_mona->connection_handler(args);
}
Inside the connection_handler() i call
ca_create_subscription()
// Code is taken from camonitor's.
//
void CuMonitorActivity::connection_handler(connection_handler_args args)
{
int nConn = 0;
/* the following three could be overridden from the cmd line in ca
monitor.c */
int floatAsString = 0;
unsigned long eventMask = DBE_VALUE | DBE_ALARM;
int reqElems = 1;
pv *ppv = ( pv * ) ca_puser ( args.chid );
if ( args.op == CA_OP_CONN_UP ) {
nConn++;
if (!ppv->onceConnected) {
ppv->onceConnected = 1;
/* Set up pv structure */
/* ------------------- */
/* Get natural type and array count */
ppv->dbfType = ca_field_type(ppv->ch_id);
ppv->dbrType = dbf_type_to_DBR_TIME(ppv->dbfType); /* Use
native type */
if (dbr_type_is_ENUM(ppv->dbrType)) /* Enums
honour -n option */
{
if (enumAsNr) ppv->dbrType = DBR_TIME_INT;
else ppv->dbrType = DBR_TIME_STRING;
}
else if (floatAsString &&
(dbr_type_is_FLOAT(ppv->dbrType) ||
dbr_type_is_DOUBLE(ppv->dbrType)))
{
ppv->dbrType = DBR_TIME_STRING;
}
/* Set request count */
ppv->nElems = ca_element_count(ppv->ch_id);
ppv->reqElems = 1;
/* Issue CA request */
/* ---------------- */
/* install monitor once with first connect */
ppv->status = ca_create_subscription(ppv->dbrType,
ppv->reqElems,
ppv->ch_id,
eventMask,
CuMonitorActivity::event_handler_cb,
(void*)ppv,
NULL);
}
}
else if ( args.op == CA_OP_CONN_DOWN ) {
nConn--;
ppv->status = ECA_DISCONN;
print_time_val_sts(ppv, reqElems);
}
}
>
>> From within connection_handler_cb I extract the current
>> CuMonitorActivity instance (stored in pv's monitor_activity field) and I
>> invoke the object's extraction method. Data is posted in the main thread.
>>
>> NOTE: I notice that connection_handler_cb is invoked from ANOTHER
>> thread, not my main thread, not my secondary thread. This should be as
>> expected I guess for things to work and given the ca_
>> enable_preemptive_callback option.
> Right, see my remarks about threading above. Note that you are allowed
> to make calls to other CA routines from within your callbacks, and in
> fact that is the recommended way to set up your subscriptions. However
> you should be careful to only subscribe for data from a PV only once; if
> the IOC gets restarted your connection handler will be called again on
> reconnect, and unless you're careful that code might create a second
> subscription to the same PV, which will result in your event callback
> being run twice or more for each data update from the IOC.
>
>> * when I post an "exitEvent" on my secondary thread from the main one
>> (e.g. the GUI is closed), "ca_context_destroy();" is invoked in my
>> secondary thread (not the Epics thread that calls connection_handler_cb
>> during monitoring - is this OK? )
> Yes, as long as the thread was attached to the CA context. You should
> attach all threads that you want to make CA calls from to your original
> context using ca_current_context() and ca_attach_context().
>
>> * Is there a way to ADD more PVs to monitor WHILE others are being
>> monitored (for example, is it legit to call create_pvs() again ) ?
> Yes, although you didn't show us your create_pvs() routine. You can add
> and remove channels and subscriptions whenever you like; about the only
> thing you can't call from within a callback routine is ca_pend_event().
OK, now it should be clear how I create the pvs.
Could you tell me how to add and remove channels and subscriptions?
As to threading, all channel/monitor setup is made up in a secondary thread.
The idea is thus to add and remove channels within that very secondary
thread.
So, as you stated, the callbacks would be invoked from the N different
threads on the event callback within the
CuMonitorActivity object.
Data, from within the event callback, is then queued and posted on the
main thread.
I have now to check whether I need to lock guard the code within the
event callback or not.
Nonetheless, if my observations are correct, that makes a good starting
point.
Thanks for Your help.
Giacomo, Elettra, Trieste, Italy
>
>> * Are there any observations/corrections to my approach and to my
>> statements above?
> Go check out epicsQt before you spend more time reinventing the wheel.
> There is a separate mailing list for EPICS users programming with Qt and
> you might want to subscribe to that:
> http://www.aps.anl.gov/epics/qti-talk/index.php
>
> - Andrew
>
- Replies:
- Re: C++ multi threaded application. Michael Davidsaver
- References:
- C++ multi threaded application. Giacomo S.
- Re: C++ multi threaded application. Andrew Johnson
- Navigate by Date:
- Prev:
Re: Base 3.15 and record initialization Johnson, Andrew N.
- Next:
Re: Archiver: Problems with disconnected PVs Gabriel de Souza Fedel
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
<2017>
2018
2019
2020
2021
2022
2023
2024
- Navigate by Thread:
- Prev:
Re: C++ multi threaded application. Andrew Johnson
- Next:
Re: C++ multi threaded application. Michael Davidsaver
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
<2017>
2018
2019
2020
2021
2022
2023
2024