Page 1 of 1

Thread and TCP

PostPosted: Thu Nov 09, 2017 6:52 am
by ingalime
Hello everyone.
In Windows we use:
Code: Select all
  IdTCPClient1->Connect();
     try
     {
      IdTCPClient1->IOHandler->Write("********");
     }
      __finally
        {
         IdTCPClient1->Disconnect();
        }

We read that for the mobile platform, this code should be allocated to a separate thread.
Please show an example.

Re: Thread and TCP

PostPosted: Thu Nov 09, 2017 12:51 pm
by rlebeau
ingalime wrote:We read that for the mobile platform, this code should be allocated to a separate thread.


Yes. Mobile OSes do not like having an app's main thread blocked for any length of time. The OS will just kill the app. Desktop platforms are more lenient about that.

ingalime wrote:Please show an example.


There are numerous examples floating around if you search online.

Re: Thread and TCP

PostPosted: Fri Nov 17, 2017 2:16 am
by ingalime
So it will be correct?
Main:
Code: Select all
TFormSent *FormSent;
TMyConnect * MyConnect;
//---------------------------------------------------------------------------
__fastcall TFormSent::TFormSent(TComponent* Owner)
   : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TFormSent::Button1Click(TObject *Sender)
{
 MyConnect = new MyConnect(false);
 MyConnect->FreeOnTerminate = true;
}


h TThread
Code: Select all
class TMyConnect : public TThread
{
private:
protected:
   void __fastcall Execute();
   void __fastcall TryMyConnect();
public:
   __fastcall TMyConnect(bool CreateSuspended);
};


cpp TThread
Code: Select all
__fastcall TMyConnect::TMyConnect(bool CreateSuspended)
   : TThread(CreateSuspended)
{
}
//---------------------------------------------------------------------------
void __fastcall TMyConnect::Execute()
{
   Synchronize(&TryMyConnect);
}
//---------------------------------------------------------------------------

void __fastcall TMyConnect::TryMyConnect()
{
 try
  {
   try{
      FormSent->IdTCPClient1->Connect();
      FormSent->IdTCPClient1->IOHandler->Write(FormSent->Memo1->Lines, true, IndyTextEncoding_UTF8());
     }
     catch(const Exception &E)
         {
         //
           }

  }
   __finally
  {
   FormSent->IdTCPClient1->Disconnect();
  }
}

Re: Thread and TCP

PostPosted: Fri Nov 17, 2017 10:58 am
by rlebeau
ingalime wrote:So it will be correct?


No. You are synchronizing *all* of TMyConnect's work back to the main thread, where it doesn't belong. You are defeating the purpose of using a worker thread. You need to get rid of the Synchronize(&TryMyConnect) call and just run the code directly in Execute() so it runs in the context of the worker thread. Use Synchronize() only when you need to run code that must run in the main thread, like accessing the UI.

Try something more like this:

Main:
Code: Select all
TFormSent *FormSent;
TMyConnect * MyConnect = NULL;
//---------------------------------------------------------------------------
__fastcall TFormSent::TFormSent(TComponent* Owner)
   : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TFormSent::Button1Click(TObject *Sender)
{
    if (!MyConnect)
    {
        MyConnect = new MyConnect();
        MyConnect->OnTerminate = &ThreadTerminated;
        MyConnect->Resume(); // or ->Start() in CB2010+
    }
}
//---------------------------------------------------------------------------
void __fastcall TFormSent::ThreadTerminated(TObject *Sender)
{
    MyConnect = NULL;
}


h TThread
Code: Select all
class TMyConnect : public TThread
{
private:
    TStringList *FLines;
    void __fastcall GetLines();
protected:
    void __fastcall Execute();
public:
    __fastcall TMyConnect();
};


cpp TThread
Code: Select all
__fastcall TMyConnect::TMyConnect()
   : TThread(true)
{
    FreeOnTerminate = true;
}
//---------------------------------------------------------------------------
void __fastcall TMyConnect::GetLines()
{
    FLines->Assign(FormSent->Memo1->Lines);
}
//---------------------------------------------------------------------------
void __fastcall TMyConnect::Execute()
{
    try
    {
        FormSent->IdTCPClient1->Connect();
        try
        {
            FLines = new TStringList;
            try
            {
                Synchronize(&GetLines);
                FormSent->IdTCPClient1->IOHandler->Write(FLines, true, IndyTextEncoding_UTF8());
            }
            __finally
            {
                delete FLines;
            }
        }
        __finally
        {
            FormSent->IdTCPClient1->Disconnect();
        }
    }
    catch(const Exception &E)
    {
        //
    }
}


Or this, which eliminates the need for using Synchronize() to get the data to send:

Main:
Code: Select all
TFormSent *FormSent;
TMyConnect * MyConnect = NULL;
//---------------------------------------------------------------------------
__fastcall TFormSent::TFormSent(TComponent* Owner)
   : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TFormSent::Button1Click(TObject *Sender)
{
    if (!MyConnect)
    {
        MyConnect = new MyConnect(Memo1->Lines);
        MyConnect->OnTerminate = &ThreadTerminated;
        MyConnect->Resume(); // or ->Start()
    }
}
//---------------------------------------------------------------------------
void __fastcall TFormSent::ThreadTerminated(TObject *Sender)
{
    MyConnect = NULL;
}


h TThread
Code: Select all
class TMyConnect : public TThread
{
private:
    TStringList *FLines;
protected:
    void __fastcall Execute();
public:
    __fastcall TMyConnect(TStrings *ALines);
    __fastcall ~TMyConnect();
};


cpp TThread
Code: Select all
__fastcall TMyConnect::TMyConnect(TStrings *ALines)
   : TThread(true), FLines(new TStringList)
{
    FreeOnTerminate = true;
    FLines->Assign(ALines);
}
//---------------------------------------------------------------------------
__fastcall TMyConnect::~TMyConnect()
{
    delete FLines;
}
//---------------------------------------------------------------------------
void __fastcall TMyConnect::Execute()
{
    try
    {
        FormSent->IdTCPClient1->Connect();
        try
        {
            FormSent->IdTCPClient1->IOHandler->Write(FLines, true, IndyTextEncoding_UTF8());
        }
        __finally
        {
            FormSent->IdTCPClient1->Disconnect();
        }
    }
    catch(const Exception &E)
    {
        //
    }
}


Either way, I would suggest NOT placing the TIdTCPClient on the Form at all. Especially if you ever need to have multiple connections running in parallel. Move the TIdTCPClient into the thread instead, eg:

Main:
Code: Select all
TFormSent *FormSent;
TMyConnect * MyConnect = NULL;
//---------------------------------------------------------------------------
__fastcall TFormSent::TFormSent(TComponent* Owner)
   : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TFormSent::Button1Click(TObject *Sender)
{
    MyConnect = new MyConnect("host", 12345, Memo1->Lines);
}


h TThread
Code: Select all
class TMyConnect : public TThread
{
private:
    String FHost;
    Word FPort;
    TStringList *FLines;
protected:
    void __fastcall Execute();
public:
    __fastcall TMyConnect(String AHost, Word APort, TStrings *ALines);
    __fastcall ~TMyConnect();
};


cpp TThread
Code: Select all
__fastcall TMyConnect::TMyConnect(String AHost, Word APort, TStrings *ALines)
   : TThread(false), FHost(AHost), FPort(APort), FLines(new TStringList)
{
    FreeOnTerminate = true;
    FLines->Assign(ALines);
}
//---------------------------------------------------------------------------
__fastcall TMyConnect::~TMyConnect()
{
    delete FLines;
}
//---------------------------------------------------------------------------
void __fastcall TMyConnect::Execute()
{
    try
    {
        TIdTCPClient *Client = new TIdTCPClient;
        try
        {
            Client->Host = FHost;
            Client->Port = FPort;

            Client->Connect();
            try
            {
                Client->IOHandler->Write(FLines, true, IndyTextEncoding_UTF8());
            }
            __finally
            {
                Client->Disconnect();
            }
        }
        __finally
        {
            delete Client;
        }
    }
    catch(const Exception &E)
    {
        //
    }
}

Re: Thread and TCP

PostPosted: Sat Nov 18, 2017 9:28 am
by ingalime
Thank you rlebeau for your help.
Please check my code with ActivityIndicator1:
Code: Select all
void __fastcall TFormSent::Button1Click(TObject *Sender)
{
 //read host and port from ini file   
 // String FHost;
 // Word FPort; 
  Button1->Enabled = false; //The user does not have to press the button more than once
  ActivityIndicator1->Animate = true;   
  MyConnect = new MyConnect(FHost, FPort, Memo1->Lines);
}


Code: Select all
__fastcall TMyConnect::~TMyConnect()
{
    Button1->Enabled = true
    ActivityIndicator1->Animate = false;     
   delete FLines;
}


And what do you recommend to write in catch TMyConnect::Execute?

Re: Thread and TCP

PostPosted: Mon Nov 20, 2017 11:38 am
by rlebeau
ingalime wrote:Please check my code with ActivityIndicator1:


Your worker thread is using FreeOnTerminate=true, so its destructor runs in the context of the worker thread (after Execute() exits), not in the context of the main UI thread. To update your UI when the thread is finished, use the thread's OnTerminate event, which is synchronized with the main UI thread (before the thread object is freed):

Code: Select all
void __fastcall TFormSent::Button1Click(TObject *Sender)
{
    Button1->Enabled = false;
    ActivityIndicator1->Animate = true;   
    MyConnect = new MyConnect(FHost, FPort, Memo1->Lines);
    MyConnect->OnTerminate = &ThreadTerminated;
    MyConnect->Resume(); // or ->Start()
}

void __fastcall TFormSent::ThreadTerminated(TObject *Sender)
{
    Button1->Enabled = true;
    ActivityIndicator1->Animate = false;     
}


Code: Select all
__fastcall TMyConnect::TMyConnect(String AHost, Word APort, TStrings *ALines)
    : TThread(true), FHost(AHost), FPort(APort)
{
    FLines = new TStringList;
    FLines>Assign(ALines);
}

__fastcall TMyConnect::~TMyConnect()
{
    delete FLines;
}


ingalime wrote:And what do you recommend to write in catch TMyConnect::Execute?


Write whatever you want. Preferably, you shouldn't have the catch at all. That way, an uncaught exception will terminate the thread and populate the thread's FatalException property, which you can check to know if it terminated gracefully or abortively:

Code: Select all
void __fastcall TFormSent::ThreadTerminated(TObject *Sender)
{
    Button1->Enabled = true;
    ActivityIndicator1->Animate = false;     

    if (static_cast<TMyConnect*>(Sender)->FatalException)
    {
        // something unexpected happened...
        // use FatalException as needed...
    }
}