Application.ProcessMessages is good for just one thing: keeping the GUI alive when doing lots of processing in the main thread. My initial reaction was about, that it doesn't fix your application freezing.
The main issue is resources. You can try to maximize performance by allocating everything you're going to need at startup, or you can allocate and free things on demand. If it is just small pieces of memory, that's easy and fast. But quite a lot of things have to ask for access, from the OS, a library or background process. The obvious ones are files and sockets. Like, the connection to a database.
So, if you're not running just a single loop with pre-allocated objects, but do a lot of different tasks, that require a lot of Create and Free, it is very likely that your application starts freezing randomly. Application.ProcessMessages won't help here. Some well-placed Sleep(0) calls probably will. And yes, you should probably split them up in different processes and/or threads.
That's why you make a single connection to a database and reuse that. And use transactions.
The MS Thread architecture is basically flawed in that it relies on shared memory and that named pipes or other easy inter-process communication methods are barely supported. It's fine to use sockets for inter-process communication, but not between threads, as you need another thread to handle the socket.
It seems like a good thing to put critical sections around every dubious block of code, use lock .. unlock wherever it looks useful and use synchronize to update that single copy of the variable. Which essentially serializes your application and creates a lot of potential race conditions.
Blocking threads are another matter. If I look in Task Manager, I see that there are 217 processes and 3589 treads running, and 116541 handles in use. Most of those threads are blocked and waiting for input. That's totally fine. And most of those handles are locks on dynamically allocated resources. So, there are a lot of resources allocated (like those files and sockets) through processes that cannot immediately respond to changes. They poll them in a loop, or only use them synchronized (wait for completion). Those need enough CPU time to complete the request and/or run through a loop polling them.
So, it doesn't matter if you serialize and synchronize everything, but then threads make no sense. In all other cases, asynchronous requests make a lot of sense, but you would want a tread waiting for completion for all requests. On Linux this is a totally different matter.