How to download multiple files at once with TIdHTTP - multithreading

I'm currently using TIdHTTP from Indy with my Embarcadero C++Builder 10.1 Berlin. I have read some online tutorials on how to make TIdHTTP multi-threaded, but the main issue is I already tested this component in a thread.
So here is how it works. I created a thread object and I made a function to download a file in that thread, apparently the thread works fine and the file get downloaded to my Disk. But, when I create additional threads for file downloads, the first thread stops. I don't want that, I want both files to continue to download at the same time (without pausing the first thread), like in IDM (Internet Download Manager).
The thread class is like in the code below:
class TIdHTTPThread : public TThread
{
protected:
void __fastcall Execute();
void __fastcall PutDownloadedFile();
public:
__fastcall TIdHTTPThread(bool CreateSuspended);
void __fastcall IdHTTPBeginWork(TObject *ASender, TWorkMode AWorkMode,
__int64 AWorkCountMax);
void __fastcall IdHTTPWork(TObject *ASender, TWorkMode AWorkMode,
__int64 AWorkCount);
void __fastcall IdHTTPEndWork(TObject *ASender, TWorkMode AWorkMode);
void __fastcall DownloadFile(UnicodeString AFileURL, UnicodeString AFileDest);
void __fastcall CreateQueue(TWinControl* wcParent, TAlign alAlign);
private:
TIdHTTP* IdHTTP;
TMemoryStream* msMemoryStream;
UnicodeString uFileURL;
UnicodeString uFileDest;
int iDownProgress;
int iFileSize;
int iMaxProgress;
int iDownSpeed;
TWinControl* wcParent;
TIFDQueue *ifdQueue;
};
Please don't bother about the additional properties and methods in the class, I just want to achieve what I need in my question.
CPP File:
void __fastcall TIdHTTPThread::CreateQueue(TWinControl* wcParent, TAlign alAlign)
{
this->wcParent = wcParent;
ifdQueue = new TIFDQueue(this->wcParent, alAlign);
}
void __fastcall TIdHTTPThread::IdHTTPBeginWork(TObject *ASender, TWorkMode AWorkMode,
__int64 AWorkCountMax)
{
this->iFileSize = AWorkCountMax;
this->iMaxProgress = AWorkCountMax;
ifdQueue->SetFileSize(this->iFileSize);
ifdQueue->SetMaxProgress(this->iMaxProgress);
ifdQueue->SetFileURL(this->uFileURL);
ifdQueue->SetFilePath(this->uFileDest);
ifdQueue->OnBeginUpdate();
}
void __fastcall TIdHTTPThread::IdHTTPWork(TObject *ASender, TWorkMode AWorkMode,
__int64 AWorkCount)
{
this->iDownProgress = AWorkCount;
this->iDownSpeed = AWorkCount / 1024;
ifdQueue->SetDownProgress(this->iDownProgress);
ifdQueue->SetDownSpeed(this->iDownSpeed);
ifdQueue->OnWorkUpdate();
}
void __fastcall TIdHTTPThread::IdHTTPEndWork(TObject *ASender, TWorkMode AWorkMode)
{
ifdQueue->OnEndUpdate();
this->Terminate();
}
//**//
void __fastcall TIdHTTPThread::DownloadFile(UnicodeString AFileURL, UnicodeString AFileDest)
{
this->uFileURL = AFileURL;
this->uFileDest = AFileDest;
}
void __fastcall TIdHTTPThread::PutDownloadedFile()
{
try {
this->msMemoryStream = new TMemoryStream;
this->IdHTTP = new TIdHTTP(NULL);
this->IdHTTP->OnWorkBegin = this->IdHTTPBeginWork;
this->IdHTTP->OnWork = this->IdHTTPWork;
this->IdHTTP->OnWorkEnd = this->IdHTTPEndWork;
this->IdHTTP->ConnectTimeout = 20000;
this->IdHTTP->ReadTimeout = 60000;
this->IdHTTP->Get(this->uFileURL, this->msMemoryStream);
this->msMemoryStream->SaveToFile(this->uFileDest);
} __finally {
delete this->msMemoryStream;
delete this->IdHTTP;
}
}
__fastcall TIdHTTPThread::TIdHTTPThread(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//---------------------------------------------------------------------------
void __fastcall TIdHTTPThread::Execute()
{
//---- Place thread code here ----
FreeOnTerminate = true;
Synchronize(&this->PutDownloadedFile);
}
//---------------------------------------------------------------------------
UPDATE:
void __fastcall TIdHTTPThread::PutDownloadedFile()
{
try {
this->CookieManager = new TIdCookieManager(NULL);
this->SSLIOHandlerSocket = new TIdSSLIOHandlerSocketOpenSSL(NULL);
this->msMemoryStream = new TMemoryStream;
// Configure SSL IOHandler
this->SSLIOHandlerSocket->SSLOptions->Method = sslvSSLv23;
this->SSLIOHandlerSocket->SSLOptions->SSLVersions = TIdSSLVersions() << sslvTLSv1_2 << sslvTLSv1_1 << sslvTLSv1;
// Setup HTTP
this->IdHTTP = new TIdHTTP(NULL);
this->ifdQueue->StopDownload(this->IdHTTP); // Function To stop download When Fired (doesn't fire imidiatly)
this->IdHTTP->OnWorkBegin = this->IdHTTPBeginWork;
this->IdHTTP->OnWork = this->IdHTTPWork;
this->IdHTTP->OnWorkEnd = this->IdHTTPEndWork;
this->IdHTTP->OnRedirect = this->IdHTTPRedirect;
this->IdHTTP->HandleRedirects = true;
this->IdHTTP->AllowCookies = true;
this->IdHTTP->CookieManager = this->CookieManager;
this->IdHTTP->IOHandler = this->SSLIOHandlerSocket;
this->IdHTTP->Get(this->uFileURL, this->msMemoryStream);
if ( this->msMemoryStream->Size >= this->IdHTTP->Response->ContentLength ) {
this->msMemoryStream->SaveToFile(this->uFileName);
}
} __finally {
delete this->msMemoryStream;
delete this->CookieManager;
delete this->SSLIOHandlerSocket;
delete this->IdHTTP;
}
}

The problem is your thread's Execute() method is doing ALL of its work inside a Synchronize() call, so ALL of its work is actually being done inside the main UI thread instead, thus serializing your downloads and defeating the whole point of using a worker thread.
DO NOT call PutDownloadedFile() itself with Synchronize(). You need to instead change your individual TIdHTTP status events to use Synchronize() (or Queue()) when updating your UI controls.

Related

TService and TThread in C++Builder

I'm not sure if I developed everything correct.
At least it's working, but is it realy correct?
But sometimes I get an error when starting the service. If I try again it's starting.
Thanks for your help.
I developed a Service which creates a Thread "Importer".
The Importer read the configuration like connection params from the registry. Here I get sometimes the problem that some parameter could not read.
The Import check for files with extension .jpg in a directory. The path of the directory is also stored in registry.
If JPG are existing in directory they will be imported in a database and removed from filesystem.
All JPG are imported the Thread "Importer" will sleep for x minutes which is also configured in registry.
The service
//---------------------------------------------------------------------------
void __fastcall TSrvCrateImage::ServiceExecute(TService *Sender) {
while (!Terminated) {
ServiceThread->ProcessRequests(false);
// MyImporter->Resume();
Sleep(1000);
}
}
//---------------------------------------------------------------------------
void __fastcall TSrvCrateImage::ServiceStart(TService *Sender, bool &Started) {
MyImporter = new Importer(false);
bool valid = ReadConfig();
if (valid) {
Started = true;
} else {
Started = false;
}
}
//---------------------------------------------------------------------------
bool __fastcall TSrvCrateImage::ReadConfig() {
UnicodeString msg = MyImporter->ReadConfig();
if (!msg.IsEmpty()) {
LogMessage(msg, EVENTLOG_ERROR_TYPE);
return false;
}
LogMessage("Configuration loaded.", EVENTLOG_INFORMATION_TYPE);
return true;
}
//---------------------------------------------------------------------------
void __fastcall TSrvCrateImage::ServiceStop(TService *Sender, bool &Stopped) {
MyImporter->Terminate();
Stopped = true;
}
The Importer
__fastcall Importer::Importer(bool CreateSuspended)
: TThread(CreateSuspended) {
m_SleepMin = 0;
m_ConfFile = new TRegConfigFile();
}
//---------------------------------------------------------------------------
void __fastcall Importer::Execute() {
try {
while (!Terminated) {
ImportImageFiles();
Sleep(m_SleepMin * 60 * 1000);
}
} catch (Exception &exception) {
SrvCrateImage->LogMessage("Importer::Execute() " + exception.ToString(), EVENTLOG_ERROR_TYPE);
}
}
Thanks a lot
I started the service and sometimes I get errors that some paramters are not existing.
I don't know what happen if a problem will occure.
Will the service and thread still work? Or not due to bad thread programming...
I see a number of issues with your code.
get rid of the TService::OnExecute event handler completely. You don't need it. It is not doing anything useful. TService will handle SCM requests internally for you when there is no OnExecute handler assigned.
What is the point of creating a new thread for the importer if you are going to make the service thread dependent on reading the config from the importer? You have two threads fighting over a single config without any synchronization between them. I would suggest having the service thread read in the config before starting the importer thread.
Your service's OnStop event handler is signaling the importer thread to terminate, but is not waiting for the thread to fully terminate, let alone destroying the thread. It needs to do both.
With that said, try something more like this:
Service:
//---------------------------------------------------------------------------
void __fastcall TSrvCrateImage::ServiceStart(TService *Sender, bool &Started) {
TRegConfigFile *ConfFile = ReadConfig();
if (ConfFile) {
MyImporter = new Importer(false, ConfFile);
Started = true;
} else {
Started = false;
}
}
//---------------------------------------------------------------------------
TRegConfigFile* __fastcall TSrvCrateImage::ReadConfig() {
String msg;
// read config directly, not from importer thread...
TRegConfigFile *ConfFile = new TRegConfigFile();
try {
msg = ...; // read from ConfFile as needed?
}
catch (Exception &exception) {
msg = exception.ToString();
}
if (!msg.IsEmpty()) {
LogMessage(msg, EVENTLOG_ERROR_TYPE);
delete ConfFile;
return NULL;
}
LogMessage("Configuration loaded.", EVENTLOG_INFORMATION_TYPE);
return ConfFile;
}
//---------------------------------------------------------------------------
void __fastcall TSrvCrateImage::ServiceStop(TService *Sender, bool &Stopped) {
if (MyImporter) {
MyImporter->Terminate();
HANDLE h = (HANDLE) MyImporter->Handle;
while (WaitForSingleObject(h, WaitHint-100) == WAIT_TIMEOUT) {
ReportStatus();
}
delete MyImporter;
}
Stopped = true;
}
Importer:
__fastcall Importer::Importer(bool CreateSuspended, TRegConfigFile *ConfFile)
: TThread(CreateSuspended) {
m_ConfFile = ConfFile;
m_SleepMin = ...; // read from m_ConfFile as needed
...
}
//---------------------------------------------------------------------------
__fastcall Importer::~Importer() {
delete m_ConfFile;
}
//---------------------------------------------------------------------------
void __fastcall Importer::Execute() {
while (!Terminated) {
ImportImageFiles();
Sleep(m_SleepMin * 60 * 1000);
}
}
//---------------------------------------------------------------------------
void __fastcall Importer::DoTerminate() {
if (FatalException) {
SrvCrateImage->LogMessage("Importer " + ((Exception*)FatalException)->ToString(), EVENTLOG_ERROR_TYPE);
}
TThread::DoTerminate();
}

How to share data between a TThread in a DLL and the main thread?

I'm writing a DLL in C++Builder XE6, that creates a separate thread (derived from TThread) to retrieve JSON data from a REST server every X seconds (using TIdHTTP), and parse the JSON data.
The thread fills a simple struct (no dynamically allocated data) with the parsed JSON data in the Execute() method of the thread:
typedef struct
{
char MyString[40 + 1];
double MyDouble;
bool MyBool;
} TMyStruct;
The thread should store the struct in a list, for example a std::vector:
#include <vector>
std::vector<TMyStruct> MyList;
The thread will add a TMyStruct to the list:
TMyStruct Data;
...
MyList.push_back(Data);
The list will be guarded by a TCriticalSection to prevent data corruption.
The DLL exports a function to retrieve a TMyStruct from MyList.
bool __declspec(dllexport) __stdcall GetMyStruct (int Index, TMyStruct* Data)
{
...
}
Only thing is, I don't know where to put MyList...
If I make MyList a global variable, it is located in the main thread's memory and GetMyStruct() can access it directly. How does the thread access MyList?
If I make MyList a member of the TThread-derived class, it is located in the thread's memory and the thread can access it directly. How does GetMyStruct() access MyList?
What is the best/prefered/common way to store MyList and access it in a different thread?
If I make MyList a global variable, it is located in the main thread's memory and GetMyStruct() can access it directly. How does the thread access MyList?
The exact same way. All threads in a process can freely access global variables within that process. For example:
#include <vector>
#include <System.SyncObjs.hpp>
typedef struct
{
char MyString[40 + 1];
double MyDouble;
bool MyBool;
} TMyStruct;
std::vector<TMyStruct> MyList;
TCriticalSection *Lock = NULL; // why not std::mutex instead?
class TMyThread : public TThread
{
...
};
TMyThread *Thread = NULL;
...
void __fastcall TMyThread::Execute()
{
TMyStruct Data;
...
Lock->Enter();
try {
MyList.push_back(Data);
}
__finally {
Lock->Leave();
}
...
}
...
void __declspec(dllexport) __stdcall StartThread ()
{
Lock = new TCriticalSection;
Thread = new TMyThread;
}
void __declspec(dllexport) __stdcall StopThread ()
{
if (Thread) {
Thread->Terminate();
Thread->WaitFor();
delete Thread;
Thread = NULL;
}
if (Lock) {
delete Lock;
Lock = NULL;
}
}
bool __declspec(dllexport) __stdcall GetMyStruct (int Index, TMyStruct* Data)
{
if (!(Lock && Thread)) return false;
Lock->Enter();
try {
*Data = MyList[Index];
}
__finally {
Lock->Leave();
}
return true;
}
If I make MyList a member of the TThread-derived class, it is located in the thread's memory and the thread can access it directly. How does GetMyStruct() access MyList?
By accessing it via a pointer to the thread object. For example:
#include <vector>
#include <System.SyncObjs.hpp>
typedef struct
{
char MyString[40 + 1];
double MyDouble;
bool MyBool;
} TMyStruct;
class TMyThread : public TThread
{
protected:
void __fastcall Execute();
public:
__fastcall TMyThread();
__fastcall ~TMyThread();
std::vector<TMyStruct> MyList;
TCriticalSection *Lock;
};
TMyThread *Thread = NULL;
...
__fastcall TMyThread::TMyThread()
: TThread(false)
{
Lock = new TCriticalSection;
}
__fastcall TMyThread::~TMyThread()
{
delete Lock;
}
void __fastcall TMyThread::Execute()
{
TMyStruct Data;
...
Lock->Enter();
try {
MyList.push_back(Data);
}
__finally {
Lock->Leave();
}
...
}
void __declspec(dllexport) __stdcall StartThread ()
{
Thread = new TMyThread;
}
void __declspec(dllexport) __stdcall StopThread ()
{
if (Thread) {
Thread->Terminate();
Thread->WaitFor();
delete Thread;
Thread = NULL;
}
}
bool __declspec(dllexport) __stdcall GetMyStruct (int Index, TMyStruct* Data)
{
if (!Thread) return false;
Thread->Lock->Enter();
try {
*Data = Thread->MyList[Index];
}
__finally {
Thread->Lock->Leave();
}
return true;
}
What is the best/prefered/common way to store MyList and access it in a different thread?
That is entirely up to you to decide, based on your particular needs and project design.

C++ Visual Studio 2017 Form. How can I update and refresh a label from a separate thread

I've tried using a few methods but can't seem to pass along the label to the new thread or have the secondary thread be able to access the label and refresh the form on the form thread. I'm trying to have it multi-threaded so the interface doesn't lock up when running commands to remote devices. Any help in having a way to update the label and refresh the form from a secondary thread would be appreciated! Here is my MyForm.h file and MyForm.cpp file:
#include <iostream>
#include <conio.h>
#include <windows.h>
namespace StudioControl2 {
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::Threading;
using namespace System::Threading::Tasks;
using namespace std;
bool GUIEvent = false;
/// <summary>
/// Summary for MyForm
/// </summary>
public ref class MyForm : public System::Windows::Forms::Form
{
public:
MyForm (void)
{
InitializeComponent();
//
//TODO: Add the constructor code here
//
}
protected:
/// <summary>
/// Clean up any resources being used.
/// </summary>
~MyForm()
{
if (components)
{
delete components;
}
}
private: System::Windows::Forms::Button^ WindowTVVmixSelect;
private: System::Windows::Forms::Label^ StatusOutput;
private: System::ComponentModel::IContainer^ components;
protected:
private:
/// <summary>
/// Required designer variable.
/// </summary>
#pragma region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
void InitializeComponent(void)
{
System::ComponentModel::ComponentResourceManager^ resources = (gcnew System::ComponentModel::ComponentResourceManager(MyForm::typeid));
this->WindowTVVmixSelect = (gcnew System::Windows::Forms::Button());
this->StatusOutput = (gcnew System::Windows::Forms::Label());
(cli::safe_cast<System::ComponentModel::ISupportInitialize^>(this->pictureBox1))->BeginInit();
this->SuspendLayout();
//
// WindowTVVmixSelect
//
this->WindowTVVmixSelect->Anchor = System::Windows::Forms::AnchorStyles::None;
this->WindowTVVmixSelect->Location = System::Drawing::Point(31, 400);
this->WindowTVVmixSelect->Margin = System::Windows::Forms::Padding(5);
this->WindowTVVmixSelect->Name = L"WindowTVVmixSelect";
this->WindowTVVmixSelect->Size = System::Drawing::Size(120, 30);
this->WindowTVVmixSelect->TabIndex = 10;
this->WindowTVVmixSelect->Text = L"Vmix";
this->WindowTVVmixSelect->UseVisualStyleBackColor = true;
this->WindowTVVmixSelect->Click += gcnew System::EventHandler(this, &MyForm::WindowTVVmixSelect_Click);
//
// StatusOutput
//
this->StatusOutput->Anchor = System::Windows::Forms::AnchorStyles::None;
this->StatusOutput->BorderStyle = System::Windows::Forms::BorderStyle::Fixed3D;
this->StatusOutput->ForeColor = System::Drawing::Color::Red;
this->StatusOutput->Location = System::Drawing::Point(232, 612);
this->StatusOutput->Margin = System::Windows::Forms::Padding(5);
this->StatusOutput->Name = L"StatusOutput";
this->StatusOutput->Size = System::Drawing::Size(120, 30);
this->StatusOutput->TabIndex = 20;
this->StatusOutput->TextAlign = System::Drawing::ContentAlignment::MiddleCenter;
//
// MyForm
//
this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
this->ClientSize = System::Drawing::Size(584, 661);
this->Controls->Add(this->WindowTVVmixSelect);
this->MinimumSize = System::Drawing::Size(600, 700);
this->Name = L"MyForm";
this->Text = L"Studio Control";
(cli::safe_cast<System::ComponentModel::ISupportInitialize^>(this->pictureBox1))->EndInit();
this->ResumeLayout(false);
this->PerformLayout();
}
#pragma endregion
private: System::Void WindowTVVmixSelect_Click(System::Object^ sender, System::EventArgs^ e) {
if (GUIEvent == false) {
GUIEvent = true;
Console::WriteLine("Window TV Vmix Select");
WindowHDMIStatus -> Text = "Vmix";
StatusOutput->Text = "Please Wait";
MyForm::Refresh();
//Creation of new thread.
ThreadWindowTVVmixSelect^ o1 = gcnew ThreadWindowTVVmixSelect();
Thread^ t1 = gcnew Thread(gcnew ThreadStart(o1, &ThreadWindowTVVmixSelect::ThreadEntryPoint));
t1->Name = "t1";
t1->Start();
}
}
ref class ThreadWindowTVVmixSelect
{
public:
void ThreadEntryPoint()
{
Sleep(2000);
Console::WriteLine("Terminating Thread Window TV Vmix Select");
//Trying to update and refresh label in MyForm.
GUIEvent = false;
}
};
};
}
#include "MyForm.h"
using namespace System;
using namespace System::Windows::Forms;
[STAThreadAttribute]
int main(array<String^>^ args) {
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
StudioControl2::MyForm form;
Application::Run(%form);
return 0;
}
As shown here MS Docs Forms Invoker
You need to create so called invoker that invokes the commands you give him in the thread that created him, so it's delegate.
Edit:
I didn't realize it's cpp, syntax is a bit different but the main idea is the same
Define and ise delegates MS Doc
I figured out how to make a Thread-Safe call from the same site I mentioned (https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls). There's a small symbol at the top right of the page (</>) that changes the code examples to one of three options, C#, VB, or C++.
Here's the code I used for setting up a new thread, and running a thread safe invoke on the form label on the UI thread from the new thread:
private:
Thread^ Thread1;
private: System::Void WindowTVVmixSelect_Click(System::Object^ sender, System::EventArgs^ e) {
if (GUIEvent == false) {
GUIEvent = true;
Console::WriteLine("Window TV Vmix Select");
StatusOutput->Text = "Please Wait";
MyForm::Refresh();
//Creation of new thread.
this->Thread1 =
gcnew Thread(gcnew ThreadStart(this, &MyForm::ThreadWindowTVVmixSelect));
this->Thread1->Start();
}
}
private:
void ThreadWindowTVVmixSelect()
{
Sleep(3000);
//SetTextWindowHDMIStatus("Vmix");
SetTextStatusOutput("");
GUIEvent = false;
}
delegate void StringArgReturningVoidDelegate(String^ text);
//Sets the status label in the form
private:
void SetTextStatusOutput(String^ text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this->StatusOutput->InvokeRequired)
{
StringArgReturningVoidDelegate^ d =
gcnew StringArgReturningVoidDelegate(this, &MyForm::SetTextStatusOutput);
this->Invoke(d, gcnew array<Object^> { text });
}
else
{
this->StatusOutput->Text = text;
}
}
Hopefully this will help anyone else looking to update a C++ CLR form from another thread.

cannot handle QNetworkAccessManager::finised signal in multithreading

I want to serialize network requests using QNetworkAccessManager. For achieving it i wrote such class:
#ifndef CLIENT_H
#define CLIENT_H
#include <queue>
#include <mutex>
#include <condition_variable>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
class Client : public QObject
{
Q_OBJECT
struct RequestRecord
{
RequestRecord(QString u, int o):url(u),operation(o){}
QString url;
int operation;
};
std::mutex mutex;
std::queue<RequestRecord*> requests;
QNetworkAccessManager *manager;
bool running;
std::condition_variable cv;
public:
Client():manager(nullptr){}
~Client()
{
if(manager)
delete manager;
}
void request_cppreference()
{
std::unique_lock<std::mutex> lock(mutex);
requests.push(new RequestRecord("http://en.cppreference.com",0));
cv.notify_one();
}
void request_qt()
{
std::unique_lock<std::mutex> lock(mutex);
requests.push(new RequestRecord("http://doc.qt.io/qt-5/qnetworkaccessmanager.html",1));
cv.notify_one();
}
void process()
{
manager = new QNetworkAccessManager;
connect(manager,&QNetworkAccessManager::finished,[this](QNetworkReply *reply)
{
std::unique_lock<std::mutex> lock(mutex);
RequestRecord *front = requests.front();
requests.pop();
delete front;
reply->deleteLater();
});
running = true;
while (running)
{
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock);
RequestRecord *front = requests.front();
manager->get(QNetworkRequest(QUrl(front->url)));
}
}
};
#endif // CLIENT_H
As one can see, there are 2 methods for requesting data from network and method process, which should be called in separate thread.
I'm using this class as follows:
Client *client = new Client;
std::thread thr([client](){
client->process();
});
std::this_thread::sleep_for(std::chrono::seconds(1));
client->request_qt();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
client->request_cppreference();
This example illustrate 2 consecutive requests to network from one thread and processing of these request in another. All works fine except my lambda is never called. Requests are sent (checked it using wireshark), but i cannot get replies. What is the cause?
as #thuga suppose the problem was in event loop. My thread always waiting on cv and thus cannot process events, little hack solve the problem:
void process()
{
manager = new QNetworkAccessManager;
connect(manager,&QNetworkAccessManager::finished,[this](QNetworkReply *reply)
{
std::unique_lock<std::mutex> lock(mutex);
RequestRecord *front = requests.front();
requests.pop();
delete front;
qDebug() << reply->readAll();
processed = true;
reply->deleteLater();
});
running = true;
while (running)
{
{
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock);
if(requests.size() > 0 && processed)
{
RequestRecord *front = requests.front();
manager->get(QNetworkRequest(QUrl(front->url)));
processed = false;
QtConcurrent::run([this]()
{
while (running)
{
cv.notify_one();
msleep(10);
}
});
}
}
QCoreApplication::processEvents();
}
}
};
it's not beautiful obvious since it is using 3 threads instead of 2, but it is Qt with this perfect phrase:
QUrl QNetworkReply::url() const Returns the URL of the content
downloaded or uploaded. Note that the URL may be different from that
of the original request. If the
QNetworkRequest::FollowRedirectsAttribute was set in the request, then
this function returns the current url that the network API is
accessing, i.e the url emitted in the QNetworkReply::redirected
signal.

QT5 Cross-thread communication, slot not called in "this" context

I have an object MainWorker ran as a separate thread thanks to moveToThread method.
MainWorker has a member SubWorker which is also ran as a separate thread. Both threads are working in infinite loops.
The idea is, MainWorker and SubWorker both perform some separate computations. Whenever SubWorker is done computing, it should notify MainWorker with the result.
Therefore I intuitively made first connection between signal emitted by SubWorker and a slot of MainWorker, but it wasn't working, so I made two more connections to rule out some potential problems:
connect(subWorker, &SubWorker::stuffDid, this, &MainWorker::reportStuff)); //1
connect(subWorker, &SubWorker::stuffDid, subWorker, &SubWorker::reportStuff); //2
connect(this, &MainWorker::stuffDid, this, &MainWorker::reportStuffSelf); //3
It seems, that what is not working is exactly what I need - cross thread communication, because connection 2 and 3 works as expected. My question is: How do I make connection 1 work?
Edit: Apparently, after Karsten's explanation, it is clear that infinite loop blocks the EventLoop. So the new question is, how can I send messages (signals, whatever) from an infinite loop thread to its parent thread?
I am new to Qt, there is a high chance that I got it completely wrong. Here goes the minimal (not)working example:
MainWorker.h
class MainWorker : public QObject
{
Q_OBJECT
public:
MainWorker() : run(false) {}
void doStuff()
{
subWorker = new SubWorker;
subWorkerThread = new QThread;
subWorker->moveToThread(subWorkerThread);
connect(subWorkerThread, &QThread::started, subWorker, &SubWorker::doStuff);
if(!connect(subWorker, &SubWorker::stuffDid, this, &MainWorker::reportStuff)) qDebug() << "connect failed";
connect(subWorker, &SubWorker::stuffDid, subWorker, &SubWorker::reportStuff);
connect(this, &MainWorker::stuffDid, this, &MainWorker::reportStuffSelf);
subWorkerThread->start();
run = true;
while(run)
{
QThread::currentThread()->msleep(200);
emit stuffDid();
}
}
private:
bool run;
QThread* subWorkerThread;
SubWorker* subWorker;
signals:
void stuffDid();
public slots:
void reportStuff()
{
qDebug() << "MainWorker: SubWorker did stuff";
}
void reportStuffSelf()
{
qDebug() << "MainWorker: MainWorker did stuff (EventLoop is not blocked)";
}
};
SubWorker.h
class SubWorker : public QObject
{
Q_OBJECT
public:
SubWorker() : run(false) {}
void doStuff()
{
run = true;
while(run)
{
qDebug() << "SubWorker: Doing stuff...";
QThread::currentThread()->msleep(1000);
emit stuffDid();
}
}
private:
bool run;
public slots:
void reportStuff()
{
qDebug() << "SubWorker: SubWorker did stuff";
}
signals:
void stuffDid();
};
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MainWorker *mainWorker = new MainWorker;
QThread *mainWorkerThread = new QThread;
mainWorker->moveToThread(mainWorkerThread);
QObject::connect(mainWorkerThread, &QThread::started, mainWorker, &MainWorker::doStuff);
mainWorkerThread->start();
return a.exec();
}

Resources