I am using mutiple connection using ODBC. In whole project I am using the same connection, but create, use and destory TQuery object. Now I am going to use connection in threads and came to know Delphi BDE provides TSession Class for that. I want to know how to use TSession for concurrent operation, please provide code sample for that if possible.
While I agree that the BDE is old, it is possible to create thread-safe access to the database using the BDE and TSessions.
Consider this. When two copies of the same application are running at the same time, the database engine or database server distinguishes between the two instances for the purpose of record and table locking. This distinction is possible because each application uses a separate connection, or in the case of the BDE, session.
The session is represented by a TSession instance. In single threaded projects the TSession is created for you. If you want to connect to the BDE with two or more threads, each should have its own TSession.
Using multiple TSessions is demonstrated here, in this really old code example that I dug up (it is old, and I would do it differently today, but you asked for it). The trick is that each session needs to have the same network directory and have a unique private directory. Here is the TThread descendant:
type
TWriteData = class(TThread)
private
FSQL: String;
FFileName: String;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean; const SQL: String;
const FileName: String); override; overload;
end;
Here is the overridden constructor:
constructor TWriteData.Create(CreateSuspended: Boolean;
const SQL: String; const FileName: String);
begin
inherited Create(True);
FSQL := SQL;
FFileName := String;
end;
And here is the execute method. Importantly, the TSession.PrivateDir is set to a unique directory name (based on the ThreadID). Could also use a GUID, or some other value, as long as it is unique. Note also that Session1 is a TSession component on the data module, and Query1 is a TQuery that uses a TDatabase (Database1), which in turn uses Session1. Session is a variable declared in the Bde.DBTables unit. This variable refers to the default TSession that the BDE creates for the BDE TDataSets that are active in the primary thread of execution.
procedure TWriteData.Execute;
var
DataMod: TDataModule1;
AppDir: String;
begin
AppDir := ExtractFilePath(Application.ExeName);
DataMod := TDataModule1.Create(nil);
try
with DataMod do
begin
//All sessions need a unique private directory
Session1.PrivateDir := AppDir + IntToStr(Self.ThreadID);
//All sessions share a common network control file
Session1.NetFileDir := Session.NetFileDir;
ForceDirectories(Session1.PrivateDir);
try
Query1.SQL.Text := FSQL;
ClientDataSet1.Open;
ClientDataSet1.SaveToFile(AppDir + FFileName);
ClientDataSet1.Close;
finally
SysUtils.RemoveDir(Session1.PrivateDir);
end; //try
end; //begin
finally
DataMod.Free;
end;
end;
I hope this helps.
Related
I offloaded all ADO hood in a separate Data Module, so a single module can be referred by several applications. All my applications basically need only two worker methonds to access data:
AdoQuery delivers a result set in a form of TADODataSet.
AdoExecute performs simple update/delete queries without fetching any results.
Here is the class structure:
type
TMyDataModule = class(TDataModule)
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
procedure pvtAdoConnect;
procedure pvtAdoExecute(const sql: string);
function pvtAdoQuery(const sql: string): TADODataSet;
public
AdoConnection: TADOConnection;
end;
Then I added two publicly exposed wrappers to class methods. I used that to avoid long class references in the calls:
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
implementation
function AdoQuery(const sql: string): TADODataSet;
begin
Result := MyDataModule.pvtAdoQuery(sql);
end;
Above are the worker function which I call from within all my forms.
AdoConnect runs only once on DataModuleCreate event. TDatModule derived from TPersistent, which allows to persist the single instance of connection throughout a runtime.
The only thing that annoys me so far - is a useless .DFM which I don't need at all.Is there any option to get rid of it?
I would handle this type of thing in one of two ways, with interfaces or with inheritance. I prefer not to expose classes to the outside world in these cases. The second one could almost be called an interface without interfaces :)
Interfaces
This version returns an interface that includes the required methods. The outside world only needs to use the interface. We keep the implementation details private. Our TMyDBClass implements the interface that we have exposed to the outside world and our global function GetDBInterface returns the single instance.
interface
uses
ADODB;
type
IMyDBInterface = interface
['{2D61FC80-B89E-4265-BB3D-93356BD613FA}']
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
end;
function GetDBInterface: IMyDBInterface;
implementation
type
TMyDBClass = class(TInterfacedObject, IMyDBInterface)
strict private
FConnection: TADOConnection;
protected
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
public
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
end;
var
FMyDBInterface: IMyDBInterface;
procedure TMyDBClass.AdoExecute(const sql: string);
begin
// ...
end;
function TMyDBClass.AdoQuery(const sql: string): TADODataSet;
begin
// ...
end;
procedure TMyDBClass.AfterConstruction;
begin
inherited;
FConnection := TADOConnection.Create(nil);
end;
procedure TMyDBClass.BeforeDestruction;
begin
FConnection.Free;
inherited;
end;
// Our global function
function GetDBInterface: IMyDBInterface;
begin
if not Assigned(FMyDBInterface) then
FMyDBInterface := TMyDBClass.Create;
Result := FMyDBInterface;
end;
initialization
finalization
FMyDBInterface := nil;
end.
Inheritance
This version uses a base class that has the required methods. This is a bit easier for people to deal with because it excludes the interface which can be complex to people starting out. Again we hide the implementation details from the user and only expose a shell of a class that includes the two methods we want people to access. The implementation of these methods is performed by a class in the implementation that inherits from the exposed class. We also have a global function that returns the instance of this class. The big advantage that the interface approach has over this approach is that the user of this object can't free the object by accident.
interface
uses
ADODB;
type
TMyDBClass = class(TObject)
public
function AdoQuery(const sql: string): TADODataSet; virtual; abstract;
procedure AdoExecute(const sql: string); virtual; abstract;
end;
function GetDBClass: TMyDBClass;
implementation
type
TMyDBClassImplementation = class(TMyDBClass)
strict private
FConnection: TADOConnection;
protected
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
public
function AdoQuery(const sql: string): TADODataSet; override;
procedure AdoExecute(const sql: string); override;
end;
var
FMyDBClass: TMyDBClassImplementation;
procedure TMyDBClassImplementation.AdoExecute(const sql: string);
begin
inherited;
// ...
end;
function TMyDBClassImplementation.AdoQuery(const sql: string): TADODataSet;
begin
inherited;
// ...
end;
procedure TMyDBClassImplementation.AfterConstruction;
begin
inherited;
FConnection := TADOConnection.Create(nil);
end;
procedure TMyDBClassImplementation.BeforeDestruction;
begin
FConnection.Free;
inherited;
end;
// Our global function
function GetDBClass: TMyDBClass;
begin
if not Assigned(FMyDBClass) then
FMyDBClass := TMyDBClassImplementation.Create;
Result := FMyDBClass;
end;
initialization
FMyDBClass := nil;
finalization
FMyDBClass.Free;
end.
Usage
Usage of these are really easy.
implementation
uses
MyDBAccess; // The name of the unit including the code
procedure TMyMainForm.DoSomething;
var
myDataSet: TADODataSet;
begin
myDataSet := GetDBInterface.AdoQuery('SELECT * FROM MyTable');
...
// Or, for the class version
myDataSet := GetDBClass.AdoQuery('SELECT * FROM MyTable');
...
end;
If you don't have any design-time non-visual components dropped onto your data module, and don't plan to ever do so, then you shouldn't need a data module at all. The whole purpose is for design-time components, and other implementations such as a Web Module or even a Windows Service Application. But not for wrapping pure code without design-time components.
Also, as mentioned in the comments, don't be confused about the meaning of TPersistent. This class is used entirely differently, and can be integrated into the IDE Object Inspector (as sub-properties within a component).
So the ideal thing for you to do is encapsulate everything in just a single class. For your purpose, a database connection...
type
TMyData = class(TObject)
private
FConnection: TADOConnection;
public
constructor Create;
destructor Destroy; override;
procedure pvtAdoConnect;
procedure pvtAdoExecute(const sql: string);
function pvtAdoQuery(const sql: string): TADODataSet;
...
end;
implementation
{ TMyData }
constructor TMyData.Create;
begin
FConnection:= TADOConnection.Create(nil);
end;
destructor TMyData.Destroy;
begin
FConnection.Connected:= False;
FConnection.Free;
inherited;
end;
As for the interpretation of being "persistent", you can create/destroy an instance of it in many ways. For example, you could use a unit's initialization and finalization sections (requiring CoInitialize) or you can have your main form initialize a global instance upon creation.
One common way of doing so is to add...
interface
function MyData: TMyData;
implementation
var
_MyData: TMyData;
function MyData: TMyData;
begin
if not Assigned(_MyData) then
_MyData:= TMyData.Create;
Result:= _MyData;
end;
initialization
_MyData:= nil;
finalization
_MyData.Free;
end.
The first time you call MyData from anywhere, a new global instance will be instantiated. Then, every further time it re-uses the same instance. This also solves the need of ActiveX and CoInitialize etc. because at this point, COM is expected to already be instantiated (which is required for ADO).
Usage of this unit would be extremely simple - use include it in the uses anywhere, and access its instance through the MyData function.
Notes
You should get out of the habit of global variables. This is asking for trouble down the road when trying to do later work. The example above shows how to accommodate for a global object instance. All other variables should be self-contained within that object, or in general one of scope / relevance. The whole control of your TADOConnection should be within here, including connecting/disconnecting, exception handling, assigning the connection string.
In case you might be interested in an alternative without DataModules alltogether, have a look at this:
https://github.com/stijnsanders/xxm/blob/master/Delphi/demo2/03%20Data%20ADO/xxmData.pas
Queries are stored in a single .sql file, which is handy to edit it in specific SQL editors or workbenches. Queries are separated with a line with --"QueryName", and loaded in a query-store on start-up. Assuming you query for smaller recordsets most of the time, the best lock and open style is read-only and static, which offers the best possible performance and least load on the database server. Getting field values uses the Collect call which also offers a little performance gain.
I am implementing an object TTextFile that is a framework for using the low level pascal file function with the OO paradigm. I want to add to developers the option to use it as a TStringList when needed in the same object, like this:
TTextFile = class(TObject)
constructor Create(FileName: String);
procedure OpenForRead;
procedure OpenForWrite;
{...}
property Content: TStringList;
end;
But my problem is that I want the Content property to use user LoadFromFile only at the first time the application uses it. Not in the Create construction, because the file might be too big, and the programmer would prefer to use the other functions in this case. The Content would be use when he knows the file he is using will not be very big.
An example of a big file is a list with all the client names and citizen ID. An example of a very tiny file is that same list, but only with the clients that are waiting to be attended in the current day.
Is it possible to be done in OO pascal? If it is not possible, I will have to make a kind of activation procedure or an overload Create and make the programmer always check if the Content is loaded before use it.
Use the concept of lazy initialization. The first time the Content property is read, load the file contents, but then keep the contents available so that subsequent accesses of the property don't re-read the file.
private
FContent: TStrings;
function GetContent: TStrings;
public
property Content: TStrings read GetContent;
function TTextFile.GetContent: TStrings;
begin
if not Assigned(FContent) then begin
FContent := TStringList.Create;
try
FContent.LoadFromFile(FFileName);
except
FContent.Free;
FContent := nil;
raise;
end;
end;
Result := FContent;
end;
Certainly this is possible.
Change your class declaration:
TTextFile = class(TObject)
constructor Create(FileName: String);
procedure OpenForRead;
procedure OpenForWrite;
function GetContent: TStringList;
{...}
property Content: TStringList read GetContent;
end;
and implement it:
function TTextFile.GetContent: TStringList;
begin
Result := TStringList.Create;
Result.LoadFromFile(FFileName); // Presumes FileName is stored in FFileName in constructor
end;
I have a parent class that can have 2 possible child classes:
TEmailBaseAccount = class
Connected: boolean;
setting: TEmailAccountSettings;
folders: TEmailAccountFolders;
procedure Connect; virtual; abstract;
end;
TEmailIMAPAccount = class(TEmailBaseAccount)
IdIMAP4: TIdIMAP4;
OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
procedure Connect; override;
end;
TlEmailPOP3Account = class(TEmailBaseAccount)
IdPOP3: TIdIPOP3;
OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
procedure Connect; override;
end;
I'm maintaining a list of the email accounts using a generic TList:
TEmailAccountList = class(TList<TEmailBaseAccount>)
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
constructor Create(AOwner: TObject);
destructor Destroy;
end;
and adding the email accounts to the list using the following code:
procedure TEmailAccountList.LoadFromStream(Stream: TStream);
var
a, c: Integer;
e: TEmailBaseAccount;
begin
c := ReadStreamInt(Stream);
for a := 0 to c - 1 do
begin
e := TEmailBaseAccount.Create(FOwnerEmailEngine);
e.LoadFromStream(Stream);
Add(e);
end;
end;
procedure TEmailAccountList.SaveToStream(Stream: TStream);
var
a, c: Integer;
e: TEmailBaseAccount;
begin
c := Count;
WriteStreamInt(Stream, c);
for a := 0 to Count - 1 do
Items[a].SaveToStream(Stream);
end;
At runtime I need to differentiate between the 2 types of child classes using something like:
if account is TEmailIMAPAccount then
...
else if account is TEmailPOP3Account then
...
I am sure that my original class declarations and the TList declaration is not suited to this requirement. What changes are needed in this scenario?
TIA.
Your type declarations are absolutely fine. Your problem is presumably that when you read an item from the stream, you don't know what type it is. You cannot use is since you don't have an instance yet.
Solve that problem by writing a type code to the stream for each instance. When you read from the stream, read the type code and use that to determine which type to instantiate.
This sort of persistence streaming is so much easier using a persistence framework that emits XML, JSON, YAML etc.
Instead of serializing the complete, highly implementation-specific objects, I would only write the account properties (mail account type, user credentials, server / port / security settings) to a file.
This allows to modify the implemenation without breaking existing setting file compatibility.
Also I would not even think about a if <object> is <class> ... else if <object> is <otherclass> ... solution. Instead, define a simple enumeration type TAccountType = (atPOP3, atIMAP) and then branch in a case structure depending on a account type property of the Account, or use the Strategy pattern.
After reading the articles "Simmering Unicode, bring DPL to a boil" and "Simmering Unicode, bring DPL to a boil (Part 2)" of "The Oracle at Delphi" (Allen Bauer), Oracle is all I understand :)
The article mentions Delphi Parallel Library (DPL), lock free data structures, mutual exclusion locks and condition variables (this Wikipedia article forwards to 'Monitor (synchronization)', and then introduces the new TMonitor record type for thread synchronization and describes some of its methods.
Are there introduction articles with examples which show when and how this Delphi record type can be used? There is some documentation online.
What is the main difference between TCriticalSection and TMonitor?
What can I do with the Pulse and PulseAllmethods?
Does it have a counterpart for example in C# or the Java language?
Is there any code in the RTL or the VCL which uses this type (so it could serve as an example)?
Update: the article Why Has the Size of TObject Doubled In Delphi 2009? explains that every object in Delphi now can be locked using a TMonitor record, at the price of four extra bytes per instance.
It looks like TMonitor is implemented similar to Intrinsic Locks in the Java language:
Every object has an intrinsic lock
associated with it. By convention, a
thread that needs exclusive and
consistent access to an object's
fields has to acquire the object's
intrinsic lock before accessing them,
and then release the intrinsic lock
when it's done with them.
Wait, Pulse and PulseAll in Delphi seem to be counterparts of wait(), notify() and notifyAll() in the Java programming language. Correct me if I am wrong :)
Update 2: Example code for a Producer/Consumer application using TMonitor.Wait and TMonitor.PulseAll, based on an article about guarded methods in the Java(tm) tutorials (comments are welcome):
This kind of application shares data
between two threads: the producer,
that creates the data, and the
consumer, that does something with it.
The two threads communicate using a
shared object. Coordination is
essential: the consumer thread must
not attempt to retrieve the data
before the producer thread has
delivered it, and the producer thread
must not attempt to deliver new data
if the consumer hasn't retrieved the
old data.
In this example, the data is a series of text messages, which are shared through an object of type Drop:
program TMonitorTest;
// based on example code at http://download.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
Drop = class(TObject)
private
// Message sent from producer to consumer.
Msg: string;
// True if consumer should wait for producer to send message, false
// if producer should wait for consumer to retrieve message.
Empty: Boolean;
public
constructor Create;
function Take: string;
procedure Put(AMessage: string);
end;
Producer = class(TThread)
private
FDrop: Drop;
public
constructor Create(ADrop: Drop);
procedure Execute; override;
end;
Consumer = class(TThread)
private
FDrop: Drop;
public
constructor Create(ADrop: Drop);
procedure Execute; override;
end;
{ Drop }
constructor Drop.Create;
begin
Empty := True;
end;
function Drop.Take: string;
begin
TMonitor.Enter(Self);
try
// Wait until message is available.
while Empty do
begin
TMonitor.Wait(Self, INFINITE);
end;
// Toggle status.
Empty := True;
// Notify producer that status has changed.
TMonitor.PulseAll(Self);
Result := Msg;
finally
TMonitor.Exit(Self);
end;
end;
procedure Drop.Put(AMessage: string);
begin
TMonitor.Enter(Self);
try
// Wait until message has been retrieved.
while not Empty do
begin
TMonitor.Wait(Self, INFINITE);
end;
// Toggle status.
Empty := False;
// Store message.
Msg := AMessage;
// Notify consumer that status has changed.
TMonitor.PulseAll(Self);
finally
TMonitor.Exit(Self);
end;
end;
{ Producer }
constructor Producer.Create(ADrop: Drop);
begin
FDrop := ADrop;
inherited Create(False);
end;
procedure Producer.Execute;
var
Msgs: array of string;
I: Integer;
begin
SetLength(Msgs, 4);
Msgs[0] := 'Mares eat oats';
Msgs[1] := 'Does eat oats';
Msgs[2] := 'Little lambs eat ivy';
Msgs[3] := 'A kid will eat ivy too';
for I := 0 to Length(Msgs) - 1 do
begin
FDrop.Put(Msgs[I]);
Sleep(Random(5000));
end;
FDrop.Put('DONE');
end;
{ Consumer }
constructor Consumer.Create(ADrop: Drop);
begin
FDrop := ADrop;
inherited Create(False);
end;
procedure Consumer.Execute;
var
Msg: string;
begin
repeat
Msg := FDrop.Take;
WriteLn('Received: ' + Msg);
Sleep(Random(5000));
until Msg = 'DONE';
end;
var
ADrop: Drop;
begin
Randomize;
ADrop := Drop.Create;
Producer.Create(ADrop);
Consumer.Create(ADrop);
ReadLn;
end.
Now this works as expected, however there is a detail which I could improve: instead of locking the whole Drop instance with TMonitor.Enter(Self);, I could choose a fine-grained locking approach, with a (private) "FLock" field, using it only in the Put and Take methods by TMonitor.Enter(FLock);.
If I compare the code with the Java version, I also notice that there is no InterruptedException in Delphi which can be used to cancel a call of Sleep.
Update 3: in May 2011, a blog entry about the OmniThreadLibrary presented a possible bug in the TMonitor implementation. It seems to be related to an entry in Quality Central. The comments mention a patch has been provided by a Delphi user, but it is not visible.
Update 4: A blog post in 2013 showed that while TMonitor is 'fair', its performance is worse than that of a critical section.
TMonitor combines the notion of a critical section (or a simple mutex) along with a condition variable. You can read about what a "monitor" is here:
http://en.wikipedia.org/wiki/Monitor_%28synchronization%29
Any place you would use a critical section, you can use a monitor. Instead of declaring a TCriticalSection, you can simple create a TObject instance and then use that:
TMonitor.Enter(FLock);
try
// protected code
finally
TMonitor.Exit(FLock);
end;
Where FLock is any object instance. Normally, I just create a TObject:
FLock := TObject.Create;
How do I access the 'NameThreadForDebugging' in a delphi Thread in Delphi 2010 ?
type
TMyThread = class(TThread)
protected
procedure Execute; override;
procedure UpdateCaption;
end;
implementation
procedure TMyThread.UpdateCaption;
begin
Form1.Caption := 'Name Thread For Debugging';
// how I get 'TestThread1' displayed in the caption
end;
procedure TMyThread.Execute;
begin
NameThreadForDebugging('TestThread1');
Synchronize(UpdateCaption);
Sleep(5000);
end;
The NameThreadForDebugging function is, as its name suggests, for debugging only. If you want to keep track of the name for other purposes, then reserve a field in the thread object and store the name there. Use that field for naming the thread and for populating your form's caption on demand.
There is no API for retrieving a thread's name because threads don't have names at the API level. NameThreadForDebugging raises a special exception that the IDE recognizes as the "name this thread" exception. It sees the exception (since it's a debugger), makes a note about the thread's name in its own internal debugging data structures, and then allows the application to continue running. The application catches and ignores the exception.
That data transfer is one-way, though. The application can send information to the debugger via an exception, but the debugger can't send data back. And the OS is oblivious to everything. To the OS, it's just like any other exception.
To do what you ask, you need to store the Name inside your thread class where you can access it, eg:
type
TMyThread = class(TThread)
protected
FName: String;
procedure Execute; override;
procedure UpdateCaption;
end;
procedure TMyThread.UpdateCaption;
begin
Form1.Caption := FName;
end;
procedure TMyThread.Execute;
begin
FName := 'TestThread1';
NameThreadForDebugging(FName);
Synchronize(UpdateCaption);
Sleep(5000);
end;
The unit DebugThreadSupport on Code Central example ID: 21893, Named Pipes, shows how to set thread name in older versions of Delphi.
AFAICS Delphi supports settings the name only. You'll have to call some windows API function to get the name.