dataexchange.h File Reference

A set of utility classes to exchange data across threads. More...

Include dependency graph for dataexchange.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

class  DataDownloader< DataType_t >
 The class used to download data. More...
 
class  DataDownloader< DataType_t >
 The class used to download data. More...
 
class  DataUploader< DataType_t >
 The class used to upload data. More...
 
class  DataUploaderDownloader< UploadedData_t, DownloadedData_t >
 A class for bi-directional communication. More...
 
class  DatumToUpload< DataType_t >
 The class used to implement RAII for data uploading. More...
 
class  DatumToUpload< DataType_t >
 The class used to implement RAII for data uploading. More...
 
class  Downloader< DataType_t >
 
class  GlobalUploaderDownloader
 The class to create or remove associations and to wake all sleeping uploaders and downloaders. More...
 
class  NewDatumEvent< DataType_t >
 The event sent to downloader when a new datum is ready. More...
 
class  NewDatumNotifiable< DataType_t >
 The interface for classes that want to be notified when a new datum is available. More...
 
class  QueueHolder< DataType_t >
 The class containing the queue and all related elements. More...
 
class  QueueHolder< DataType_t >
 The class containing the queue and all related elements. More...
 
class  QueueHolderBase
 The parent of the class containing the queue and all related elements. More...
 

Namespaces

 farsa
 A macro to deprecate functions.
 
 farsa::__DataExchange_internal
 The namespace with classes used in the implementation.
 

Detailed Description

A set of utility classes to exchange data across threads.

These classes can be used to exchange data (we will use the singular "datum" to indicate a single element) between two objects which live in different threads, one of which "produces" or "uploads" data (we will call it "uploader") and the other which "consumes" or "downloads" it (we will call it "downloader"). The uploaded elements are kept in a queue. It is possible to configure what to do when the queue is full. The possible behaviors are:

  • start overriding older elements. This is useful when the downloader is slower than the uploader (e.g. the downloader is a GUI which only has to display the current data);
  • block the uploader until at least one datum has been downloaded;
  • tell the uploader that there is no more space to hold a new datum, without blocking it;
  • increase the size of the queue. The downloader can query how many data are available and get the next datum from the queue. What happends when a new datum is available in the queue can be configured, too. The possible behaviors are:
  • nothing happends and calls to DataDownloader::downloadDatum() when the queue is empty return NULL;
  • nothing happends but calls to DataDownloader::downloadDatum() when the queue is empty block the downloader until at least one datum is available;
  • a qt event of type NewDatumEvent is sent to a QObject. For this to work, the object that receives the event must live inside a thread that has the event dispatcher running (e.g. the GUI thread). Calls to DataDownloader::downloadDatum() when the queue is empty return NULL;
  • a function is called. The notified object must inherit from NewDatumNotifiable and implement the function NewDatumNotifiable::newDatumAvailable(). Moreover the callback function must be thread-safe. Calls to DataDownloader::downloadDatum() when the queue is empty return NULL. The object that creates data must use an instance of the class DataUploader and the object that uses data must use an instance of the class DataDownloader, with the same DataType. To associate an uploader and a downloader you have to use the GlobalUploaderDownloader::attach function. The objects that are associated must not be associated with other objects, otherwise an exception is thrown. To remove an association call GlobalUploaderDownloader::detach, passing either the uploader or the downloader. Both the attach and the detach functions are thread-safe. Here is an example of the association process:
// Uploader creation
DataUploader<MyData> uploader(queueSize, fullQueueBehavior);
...
// Downloader creation (possibly in another thread)
DataDownloader<MyData> downloader(newDatumAvailableBehavior);
...
// Association
GlobalUploaderDownloader::attach(&uploader, &downloader);
...
// Removal of an association. You can alternatively pass the downloader
GlobalUploaderDownloader::detach(&uploader);

The association can only be 1:1, i.e. one downloader can only be associated with one uploader and vice-versa. The DataType used by the downloader and the uploader has the only requirement to have a constructor without arguments. To create a new datum, call DataUploader::createDatum(). The returned object will be the next element in the queue. It is possible (indeed very probable) that the returned datum is an object that has already been used before, so you shouldn't rely on the fact that is has been just created (if you need this, you can, for example, add a clear() function to DataType that resets all members). Once you have modified the datum, call DataUploader::uploadDatum(). For example:

void A::f()
{
// This call can block or return NULL depending on the FullQueueBehavior
MyData* d = m_uploader.createDatum();
// Fill data
d->...
d->...
...
m_uploader.uploadDatum();
}

A more convenient way of creating a new datum is to use the helper class DatumToUpload, which automatically calls createDatum() when it is created and uploadDatum() when it is destroyed. For example:

void A::f()
{
// This call can block or return NULL depending on the FullQueueBehavior
DatumToUpload<MyData> d(m_uploader);
// Fill data
d->...
d->...
...
// m_uploader.uploadDatum() will be called automatically before
// returning from this function (i.e. when the destructor of
// DatumToUpload is called)
}

To get the new datum, call DataDownloader::downloadDatum(). The returned element is guaranteed to remain valid until DataDownloader::downloadDatum() is called again. For example:

void B::f()
{
// In this example we use polling
while (true) {
// This call can block or retun NULL depending on the
// NewDatumAvailableBehavior
const MyData* d = m_downloader.downloadDatum();
// Uses the datum
d->...
d->...
...
}
}

It is also possible to inherit from DataUploader and DataDownloader to upload/download data. If the communication between two classes needs to be bi-directonal, it is possible to use the DataUploaderDownloader class. It has two template parameters for the data to upload and the data to download. Tha advantage is that the association can be done once for both channels. The DataUploaderDownloader class inherits from both DataUploader and DataDownloader, so it has the methods of both classes. The two ends of the channel must have (of course) complementary data types: the upload data type for one end must be the download data type for the other end and vice-versa. The functions in GlobalUploaderDownloader to create or remove associations are specialized for DataUploaderDownloader, so that you can associate both ends of the communication channel in one call. One final note about stopping all data exchanges. It is possible to call the GlobalUploaderDownloader::stopAllDataExchanges() to end all possible exchanges of data and wake all uploader/downloader. This can be necessary if e.g. you want to stop a simulation and so you need to wake up all sleeping threads so that they can terminate. That function only influences uploader and downloader created before it is called. There is no way to resume data exchanges at the moment, you have to destroy and re-create all uploaders and downloaders.

Warning
Functions in both DataUploader and DataDownloader are not thread-safe, so you must not share DataUploader or DataDownloader objects among objects which live indifferent threads.
Note
A note regarding downloader notification: it is possible that one notification is sent even if there are more that one datum available. This can happend only when the downloader is associated to the uploader after the uploader has already uploaded some data. If you are sure that the uploader hasn't uploaded any datum before the association with the downloader, then each notification means that exactly one datum has been added. It is however possible that there are fewer available data than notifications: this can happend if the notification is done via a QT event, but the event handler is called only after more than the uploader queue length data has been added. Always check that the returned datum is not NULL before using it in this situation. The callback notification mechanism doesn't have this kind of problems
As explained above, when creating a new datum it is possible that an object used before is returned. This means that you should not rely on the fact that the returned object has been just created.
When the FullQueueBehavior of an uploader is set to BlockUploader or the NewDatumAvailableBehavior of a downloader is set to NoNotificationBlocking, the upload/download function can still return NULL if all uploaders/downloaders are woken up by GlobalUploaderDownloader::stopAllDataExchanges(). This implies that you must always check the return value of downloader/uploader functions to get data.

Definition in file dataexchange.h.