Login Page - Create Account

Support Board


Date/Time: Wed, 27 Nov 2024 14:32:15 +0000



[Programming Help] - C++ threads inside a ACSIL study?

View Count: 1064

[2023-02-08 16:11:14]
User133994 - Posts: 80
Has anyone successfully used threads in a custom study?

I'm trying to use a TCP server/client architecture to share data between separate SC instances but can't seem to get a thread working in the Client custom study. A blocking client appears to work without crashing SC but when I add something like this:

std::thread watchdog([&]
   {
   std::this_thread::sleep_for(std::chrono::seconds(20));
   io_service->stop();
   });

SierraChart crashes. I know this isn't a code support forum..so I'm not asking for code review, just someone with more experience than I who might have a working thread template study that I can look at.

Even better, if you have a working method of sending data from one chart in a Sierra Chart instance to another chart in an entirely different Sierra Chart instance, that would be grand.

Thanks all
[2023-02-08 20:34:57]
User133994 - Posts: 80
Ok, now I need some expert help...I think it has to do with pointers

A) <snippet only, huge source base> this non-pointer io_service object version compiles and works as expected:

s11n_example::client* pClient = reinterpret_cast<s11n_example::client*>(sc.GetPersistentPointer(pClientKeys[0]));

boost::asio::io_service io_service;
sc.SetPersistentPointer(pClientKeys[0], new s11n_example::client(io_service, "127.0.0.1", std::to_string(12344), sc));
io_service.run();


B) this pointer to io_service object version compiles but "doesn't" work (even the graph name is not shown in the "add Custom Study" dialogue, just the scsf_Client function name shows...weird)

boost::asio::io_service* p_io_service = reinterpret_cast<boost::asio::io_service*>(sc.GetPersistentPointer(ioServiceKey));

if(p_io_service == nullptr){
  //all threads need the io_service
  sc.SetPersistentPointer(ioServiceKey, new boost::asio::io_service);
}
sc.SetPersistentPointer(pClientKeys[0], new s11n_example::client(*p_io_service, "127.0.0.1", std::to_string(12344), sc));
p_io_service->run();

Of course, I can get the pointers version with threads to work in a main.c file--easy. The problem is integrating it into a custom study scsf_ function--where the main doesn't exist and pointer logic has to be persistent...

Any assistance appreciated.

Thanks!
[2023-02-08 22:03:36]
User133994 - Posts: 80
Now, I have the "breaking" lines of code identified. No clue as to why? Please assist.


s11n_example::client* pClient = reinterpret_cast<s11n_example::client*>(sc.GetPersistentPointer(pClientKeys[0]));

if (pClient == nullptr){
try
{

boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);

sc.SetPersistentPointer(pClientKeys[0], new s11n_example::client(io_service, "127.0.0.1", std::to_string(12344), sc));


// 3 ways to attempt some threading approaches--all 3 compile, but SC crashes when I press "Add Custom Study" button
//NONE of these "thread" approaches work. They are all NON-BLOCKING--which is what I need. However, there is always a SC crash as soon as I click "Add Custom Study" button!

//1st attempt>> std::thread run_thread([&]{ io_service.run(); }); << CRASH
//2nd attempt>> boost::thread run_thread(boost::bind(&boost::asio::io_service::run, boost::ref(io_service))); << CRASH
//3rd attempt>> boost::thread run_thread([&] { io_service.run(); }); <<CRASH

io_service.run(); //<<<------ BLOCKING, but works!! (i.e. I see data from the "other chart" in messages) the study is added to the chart with no issues, however, program flow (of the study) is halted at this line...thus, no updates are possible; i.e. I need to call pClient.RequestData() on each new bar..after this line..

Reminder: the server + client works as expected with threads in separate main() programs; it is just the integration into the scsf_ custom ACSIL study that is causing the issue--specifically with the client now.
Date Time Of Last Edit: 2023-02-08 22:06:10
[2023-02-08 23:15:19]
User133994 - Posts: 80
Verified...apparently *any* std::thread crashes SC even before you get to add it to a chart. Hmmm... Hopefully, someone has a work around.

Support, Please verify my findings and share any work arounds. Thanks so much.

This simple ACSIL study CRASHES Sierra Chart as mentioned above:

(to stop the Crashes you must delete the threadTest.dll file from the "Data" folder)


#include "sierrachart.h"
#include<thread>

/****************************************************************************/

SCDLLName("threadTest")

// A dummy function
void foo(int Z)
{
SCString DebugString;
for (int i = 0; i < Z; i++)
{
DebugString.Format("example thread loop");
// sc.AddMessageToLog(DebugString, 0);
// cout << "Thread using function"
   // " pointer as callable\n";
}
}

/*============================================================================
  This example code calculates a simple moving average (30 period by
  default).
----------------------------------------------------------------------------*/
SCSFExport scsf_SimpMovAvg(SCStudyInterfaceRef sc)
{
  SCSubgraphRef Subgraph_Average = sc.Subgraph[0];
  
  SCInputRef Input_Length = sc.Input[0];
  
std::thread th1(foo, 3);
  // Set configuration variables
  if (sc.SetDefaults)
  {
    // Set the configuration and defaults
    
    sc.GraphName = "Simple Moving Average";
    
    sc.StudyDescription = "Example function for calculating a simple moving average.";
    
    // Set the region to draw the graph in. Region zero is the main
    // price graph region.
    sc.GraphRegion = 0;

    sc.ValueFormat = VALUEFORMAT_INHERITED;
    
    // Set the name of the first subgraph
    Subgraph_Average.Name = "Average";
    
    // Set the color, style and line width for the subgraph
    Subgraph_Average.PrimaryColor = RGB(0,255,0);
    Subgraph_Average.DrawStyle = DRAWSTYLE_LINE;
    Subgraph_Average.LineWidth = 2;
    
    // Set the Length input and default it to 30
    Input_Length.Name = "Length";
    Input_Length.SetInt(30);
    Input_Length.SetIntLimits(1,MAX_STUDY_LENGTH);
    Input_Length.SetDescription("The number of bars to average.");
    
    sc.AutoLoop = 1;
    
    sc.AlertOnlyOncePerBar = true;
    
    // Must return before doing any data processing if sc.SetDefaults is set
    return;
  }
  
  // Do data processing
  
  // Set the index of the first array element to begin drawing at
  sc.DataStartIndex = Input_Length.GetInt() - 1;
  
  
  // Calculate a simple moving average from the base data
  sc.SimpleMovAvg(sc.Close,Subgraph_Average,Input_Length.GetInt());
  
  
  if (sc.CrossOver(Subgraph_Average, sc.Close))
  {
    // Since we are using auto-looping we do not specify the Index parameter.
    sc.SetAlert(1, "Moving average has crossed last price.");
  }
}


[2023-02-09 04:41:44]
ondafringe - Posts: 286
I know virtually nothing about the deeper level C++ stuff, but I got curious enough to try your thread test example.

Coded as you have it, my study crashed, as well. Then I read something about join() and detach() and that you should explicitly do one or the other for a thread, so I added the join, like so:


std::thread th1(foo, 3);
th1.join();

I added my study and it acted like it was going to crash, but then my study came up and worked as expected.

So I think what was happening, since my study is continuously looping, the thread is continuously being created, called, etc., and at some point, it hit some max and the thread(s) was/were terminated.

Okay, so then I created a bool and wrapped the thread code inside a conditional based on that bool so the thread was only created and executed one time.

I then stepped through it in debug mode and everything worked as expected. No crash. No looping issues. Then I ran it without going into debug and no issues.

Again, I don't really know what I'm doing, but maybe this will help give you an idea of what you need to do to resolve your issues.
Date Time Of Last Edit: 2023-02-09 05:09:10
[2023-02-09 09:12:41]
User431178 - Posts: 544
Verified...apparently *any* std::thread crashes SC even before you get to add it to a chart.

The use of threads is not the cause here, all you have verified is that improper use of threads can crash the program.

As per the answer above, when the study function returns (the thread goes out of scope), the thread destructor is called and as your thread is still in a joinable state, the standard behavior is for the thread destructor to immediately terminate the program.

Also, bearing in mind the fact that the SetDefaults block is executed when you open the Add Custom Study window, in other word before the study is actually added to a chart, carefully consider when exactly you should be performing your thread initializtion operations.

ACSIL Interface Members - Variables and Arrays: sc.SetDefaults
ACSIL Programming Concepts: Study Initialization/Unitialization

Your preceding examples are not very helpful as they don't give any clue about the context in which they are used, only that you are using them in a study function.
[2023-02-09 15:31:05]
ondafringe - Posts: 286
@User431178

Thank you for the explanation.
[2023-02-09 20:35:22]
User133994 - Posts: 80
User431178 and ondafringe,

Thanks so much for your insight.

no more crashes, yeah!!

Correct implementation:

1) Add these global variables (so they never go out of scope):

boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
std::vector<std::thread> threads;

2) Updated code block I originally sent (only 1 persistent variable, need to re-consider adding globals as persistent variables in the future):

s11n_example::client* pClient = reinterpret_cast<s11n_example::client*>(sc.GetPersistentPointer(pClientKeys[0]));

if (pClient == nullptr){
try
{

sc.SetPersistentPointer(pClientKeys[0], new s11n_example::client(io_service, host, port, sc));

auto count = std::thread::hardware_concurrency() * 2;
if (count > 2) count = 2; //debugging shows an excepetion after 2? (on R&D machine); count = 8 while debugging prior to me adding this line

for(int n = 0; n < count; ++n)
{
  threads.emplace_back([&]
{
io_service.run();
};
}
.
.
.

3) Add these statements on uninitialization (i.e. LastCallToFunction):

if (sc.LastCallToFunction){
if (pClient == nullptr)
   //do nothing
   int a = 1;
else
{
io_service.stop(); // That's OK, io_context::stop is thread-safe
// work.reset(); compiler can't find?

for(auto& thread : threads)
{
if(thread.joinable())
{
  thread.join();
}
}
delete pClient;
}
}

So yes, I didn't have a method to properly shut down such threads; and, this is just the beginning framework, I have more things to iron out. But this works. Thanks again.
[2023-02-09 21:14:13]
ondafringe - Posts: 286
Glad you found it helpful. Good luck with the rest of your project.

To post a message in this thread, you need to log in with your Sierra Chart account:

Login

Login Page - Create Account