Thread and TCP

This is the forum for miscellaneous technical/programming questions.

Moderator: 2ffat

Thread and TCP

Postby ingalime » Thu Nov 09, 2017 6:52 am

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.
ingalime
Active Poster
Active Poster
 
Posts: 13
Joined: Wed Apr 12, 2017 3:22 am

Re: Thread and TCP

Postby rlebeau » Thu Nov 09, 2017 12:51 pm

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.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1450
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Thread and TCP

Postby ingalime » Fri Nov 17, 2017 2:16 am

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();
  }
}
ingalime
Active Poster
Active Poster
 
Posts: 13
Joined: Wed Apr 12, 2017 3:22 am

Re: Thread and TCP

Postby rlebeau » Fri Nov 17, 2017 10:58 am

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)
    {
        //
    }
}
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1450
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Thread and TCP

Postby ingalime » Sat Nov 18, 2017 9:28 am

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?
ingalime
Active Poster
Active Poster
 
Posts: 13
Joined: Wed Apr 12, 2017 3:22 am

Re: Thread and TCP

Postby rlebeau » Mon Nov 20, 2017 11:38 am

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...
    }
}
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1450
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Thread and TCP

Postby ingalime » Thu Nov 23, 2017 11:50 am

Thank you very much!

P.S.
I rty to use: IdTCPClient1->Connect();
without thread, in main thread. I use tablet with 2 Gb memory. I test one week. No problem.
Do I need realy separate thread for this device?
ingalime
Active Poster
Active Poster
 
Posts: 13
Joined: Wed Apr 12, 2017 3:22 am


Return to Technical

Who is online

Users browsing this forum: Bing [Bot] and 10 guests