Monday, November 24, 2014

Why Keep Your End Users Waiting? Try Multi-Threading With ShellExec API Calls...

Have you ever been using an application and decided to use the context sensitive help by pressing F1, only to be greeted by an indeterminately long pause while the application you were using waits for the help process to start?

Maybe an email link in an application that opens up your email client or web browser?

In this wonderful day and age of multicore high clock rate processors why does an application sit unrefreshed and essentially unresponsive while it waits for another task to start such as that of the help window, the email link or web browser link?

The truth is that it doesn't have to wait at all.

Let's take a look at the Win32 API call ShellExec for a second. A call to ShellExec runs in the application's main thread. Whenever you make a call to ShellExec that opens another process, it holds up the main VCL or application process. This is the same in many other commercial applications as well. Don't get me wrong, having the ability to run an application through an API call via a command line, so that the operating system determines how to handle the call is a great thing.

Now that we know the culprit holding up our little bit of time, what can we do?

One of the wonderful things about the VCL is encapsulation and fortunately it encapsulates threads quite well for this kind of situation. What we want to do is anytime we make a call to ShellExec, we instantiate it in a separate thread of its own, so that our application and more importantly our interface isn't held up while the user waits for the requested task to start. This is a computer after all and if it can do multiple things at once, shouldn't we be able to as well being one of the many wonderful marvels of life on this planet?

So using the Threads unit, we can simply implement a solution for any call to ShellExec call via the use of the following utility unit (tested with Delphi XE4 but should work with current and past versions as well):

{*******************************************************}
{                                                       }
{       ThreadedShellExec                               }
{                                                       }
{       Copyright (C) 2014 Brian Joseph Johns           }
{       Use and share freely                            }
{                                                       }
{*******************************************************}
unit ThreadedShellExec;

interface

uses
  System.Classes, Winapi.Windows;

type
  TShellExecThread = class(TThread)
  private
    { Private declarations }
    FCommandline: string;
  protected
    procedure Execute; override;
    property Commandline: string read FCommandline write FCommandline;
  end;

  procedure ThreadedShellExecute(ACommandline: string);

implementation

uses WinAPI.ShellAPI;

procedure ThreadedShellExecute(ACommandline: string);
begin
  with TShellExecThread.Create(True) do
  begin
    FreeOnTerminate := True;
    Commandline := ACommandline;
    {$if CompilerVersion >= 21}
    Start;
    {$else}
    Resume;
    {$endif}
  end;
end;

{ TShellExecThread }

procedure TShellExecThread.Execute;
begin
  { Place thread code here }
   ShellExecute(0,'open',PWideChar(FCommandline),nil,nil,SW_SHOWNORMAL);
end;

end.

Now, the next part is simple. We just replace our call to ShellExec with the above defined procedure ThreadedShellExecute.


Let's suppose that on a TLabel component on our application, we have defined an OnClick event that we want to open the end user's default web browser to a particular web site. Here's the OnClick handler that we might define:

implementation

uses ThreadedShellExec;

TForm1.Label1Click(Sender: TObject);
begin
  ThreadedShellExecute('http://delphitidbits.blogspot.ca');
end;

...

or alternately to open an email link and fill in the subject line for the end user:

implementation

uses ThreadedShellExec;

TForm1.Label1Click(Sender: TObject);
begin
  ThreadedShellExecute('mailto:fav.inbox@gmail.com?   subject=Requests%20And%20Feedback');
end;

As you can see, it simplifies the calling process and if you need more functionality from the ShellExec or to expand its functionality, it would require very little modification.

The best part is that when the end user clicks that label, they won't be kept waiting while their email client or browser loads.

Happy Coding.

Brian Joseph Johns
http://delphitidbits.blogspot.ca






No comments:

Post a Comment