Web Service, Multithreaded, Logging to Database

  • Thread starter Thread starter Stephen Carson
  • Start date Start date
S

Stephen Carson

I'm trying to build a Web Service that will kick off threads as logging
requests come in. These threads will then log to the database. I have
been able to make a simple Web Service. I have been able to get a
simple multi-threaded example working. I have been used the
SqlDataAdapter and DataSet classes extensively to access databases in a
deployed Windows Service. But I'm getting confused how to put the
pieces together.

For example, I want to make the class that I'm going to hand off to the
thread able to access the database. The way I learned to do that is
with a Component Class:
__gc public class LogItem : public System::ComponentModel::Component

But in the docs, I note this:
"Any public static (Shared in Visual Basic) members of this type are
safe for multithreaded operations. Any instance members are not
guaranteed to be thread safe."

But that makes the Component Class pretty darn useless for my purpose.
How do I do thread safe database access?

If anyone has seen an example of a multi-threaded Web Service talking
to a DB, please let me know. It seems like a fairly obvious thing to do
with a Web Service.

Thanks,
Stephen W. Carson
 
Stephen said:
But in the docs, I note this:
"Any public static (Shared in Visual Basic) members of this type are
safe for multithreaded operations. Any instance members are not
guaranteed to be thread safe."

When we say something is not thread safe, it doesn't mean it can't be
used in a thread. It just means that if two threads write the same
object at the same time, you'll get into an undefined state. As long as
the object is used exclusively by a single thread only, you don't have
to worry about thread safety issues. If an object is shared by multiple
threads, you have to ensure mutual exclusivity on your own (for example,
by using a critical section). Most objects are thread unsafe, because
it's a tremendous overhead to add thread safety, and only you can do it
efficienty at the application level. Those who created the objects don't
know anything about your threads and how they interact.
But that makes the Component Class pretty darn useless for my purpose.
How do I do thread safe database access?

That has nothing to do with the component, it has to do with the
database. Which database are you using? There are single-user (embedded)
databases, which lock the database while it's open, thus not allowing
anyone else to open it. Those are usually very lightweight databases and
pretty useless in your case. However, every serious database engine
designed for a multiuser environment supports transactions and access to
the engine by multiple threads at the same time.

What you can't do is to drop a single database component on your form,
and access that by multiple threads at the same time. But if you create
a dedicated database logger for each of your threads, they won't
conflict. Your LogItem object should be a private member of your thread,
inaccessible from the outside, and you'll be fine.

In other words, you can access a database by multiple threads, but each
thread has to use its own database component, because the database
access component itself is not thread safe, the database engine usually
is, except those ultra-thin embedded engines.

Tom
 
Tamas said:
When we say something is not thread safe, it doesn't mean it can't be
used in a thread. It just means that if two threads write the same
object at the same time, you'll get into an undefined state. As long as
the object is used exclusively by a single thread only, you don't have
to worry about thread safety issues.

You've cleared up my confusion here. I was reading "not thread safe" as
"don't use this in a multi-threaded environment at all".
That has nothing to do with the component, it has to do with the
database. Which database are you using? There are single-user (embedded)
databases, which lock the database while it's open, thus not allowing
anyone else to open it. Those are usually very lightweight databases and
pretty useless in your case. However, every serious database engine
designed for a multiuser environment supports transactions and access to
the engine by multiple threads at the same time.

We're using SQL Server so I'm sure the database can handle it.
What you can't do is to drop a single database component on your form,
and access that by multiple threads at the same time. But if you create
a dedicated database logger for each of your threads, they won't
conflict. Your LogItem object should be a private member of your thread,
inaccessible from the outside, and you'll be fine.

In other words, you can access a database by multiple threads, but each
thread has to use its own database component, because the database
access component itself is not thread safe, the database engine usually
is, except those ultra-thin embedded engines.

OK, I believe my approach is going to work then. The basic idea is that
for each logging request that comes in, a LogItem object is created and
handed off to a thread, like so:
LogItem * logItem = new LogItem(various,info,to,log);
Thread * thread = new Thread ( new ThreadStart( logItem,
&LogItem::LogIt ) );
thread->Start();

The actual database access would happen inside LogIt() with each
logItem object having its own sqlDataAdapter object, etc.

Sound like I'm on the right track?

Thanks very much for your lengthy and very helpful reply!

Stephen W. Carson
 
Stephen Carson said:
Sound like I'm on the right track?

Yes, that should work fine.

You should be aware of potential deadlock issues between multiple database
accessors. For a logging scenario, I'd expect that you'll be using
connections that are in auto-commit mode (no explicit transaction), and that
the loggers will basically be doing insert-only transactions. In that case,
there's nothing to worry about with regard to deadlocks.

-cd
 
You should be aware of potential deadlock issues between multiple database
accessors. For a logging scenario, I'd expect that you'll be using
connections that are in auto-commit mode (no explicit transaction), and that
the loggers will basically be doing insert-only transactions. In that case,
there's nothing to worry about with regard to deadlocks.

Insert-only transactions are exactly what I'm planning on doing. Is
there anything special I need to do to ensure my connections are in
"auto-commit mode"? I'm making use of the auto-generated DataSet like
so:

DataRow * newRow = dsTest1->Tables->Item["SWC_Test"]->NewRow();
newRow->Item["French"] = m_temp;
dsTest1->Tables->Item["SWC_Test"]->Rows->Add(newRow);
 
Stephen said:
Insert-only transactions are exactly what I'm planning on doing. Is
there anything special I need to do to ensure my connections are in
"auto-commit mode"?

No. Unless you take specific steps to make it otherwise, your connections
will be auto-commit.

-cd
 
Thanks very much to Tamas and Carl for their help. I got a little toy
Web Service working last night that launches off a DB inserting thread
for each request. I then put in a little loop to rapid fire launch 100
of these threads. They all wrote to the database with no problem. 100
in less than a second is way better performance than I expect to need
for this service so this is looking like a robust logging solution.

Stephen W. Carson
 
Back
Top