Wednesday, March 20, 2013

Another Holy Grail achieved: sending email from a separate thread

As I have noted, I have spent no small amount of time updating and expanding the use of email in the Occupational Psychologist's management program. Email is now sent via GMail and works very nicely ... except for one not so small problem: when one is sending an email with an attached file, the program apparently stops working for a minute or so. The solution to this problem is to send the email via a separate thread but until now I have lacked the programming knowledge to implement this.

After playing around with a few ideas (and not succeeding), I decided to ask the question on Stack Exchange and received perfectly cromulent code from Mr Indy, Remy Lebeau. As written, this works perfectly.

What is missing is logging - previously, whilst the email was being sent, there was a certain amount of diagnostic information presented to the user which basically meant "Although the program appears to be hung, it is doing something which takes some time so please wait". Due to well known technical problems with displaying in the main program output created by a worker thread, Remy's code does not address this problem.

I attempted to add a few types of logging to the program, none of which were particularly successful. I am reminded of the old academic adage, which has a lecturer talking to a student about a paper that the student has produced: "There is much here that is original and there is much which is correct. Unfortunately, what is correct is not original, and what is original is not correct".

One partially successful attempt had the thread writing its log to a file which was opened and closed all the time. This seemed to work until the program sent two emails almost simultaneously: one thread could write to the log whereas the second couldn't. As usual, one has to be away from the keyboard in order to find a solution. The initial improvement which my thinking suggested would have each thread create a string list and write all its logging to its list; after sending the email, the thread saves the stringlist's contents to a text file (each thread has a separate file). Then presumably something would concatenate the text files so that the program could display the entire log. This would require some form of identification within the log so that the log of two emails which were sent within seconds of each other can be traced.

The problem with this solution is the 'something' that concatenates; if two people are running the program at the same time (as frequently happens), then they may interfere with each other. Running a daemon - a separate program which runs only on one computer - would solve this problem, but I am currently considering a third option: instead of writing the stringlist's contents to disk, the contents get written to the database. This means that each thread has to have its own connection to the database. This actually is simpler than it seems.

Following is the code which I added at the end of the 'execute' procedure in Remy's code.

  messagebeep ($40);

  ibdb:= TIBDatabase.Create (nil);
  with ibdb do
    loginprompt:= false;
    params.add ('password=masterkey');
    params.add ('user_name=sysdba');
    databasename:= dbdir;
    sqldialect:= 1;
    connected:= true;

  trans:= TIBTransaction.create (nil);
  trans.defaultdatabase:= ibdb;

  with TIBQuery.create (nil) do
    database:= ibdb;
    transaction:= trans;
    // add primary key via trigger
    sql.Add ('insert into emailog (msgnum, curdate, details)');
    sql.add ('values (:p1, :p2, :p3)');
    ParamByName ('p1').AsString:= FMsgNum;
    ParamByName ('p2').asdate:= date;
    for i:= 1 to log.count do
      ParamByName ('p3').AsString := log[i-1];

A few notes: 
  • The 'messagebeep' command has a parameter - I've always passed the value 0 to this function but it turns out that there are specific values producing specific sounds. The idea of including it in the thread is to give the user confirmation that the email has been sent. Making a noise doesn't compromise thread security.
  • 'dbdir' is a global variable whose value is the location of the database.
  • Databases are optimised for handling large amounts of data arriving in a short period of time. In the example above, there is a very small loop which basically sends some strings (in practice, seven) to the database, one after each other. Two threads could be doing this almost simultaneously - but the database can handle this easily.

No comments: