How to do asynchronuous programming in Delphi?

2019-04-24 13:11发布

I have an application, where most of the actions take some time and I want to keep the GUI responsive at all times. The basic pattern of any action triggered by the user is as follows:

  1. prepare the action (in the main thread)
  2. execute the action (in a background thread while keeping the gui responsive)
  3. display the results (in the main thread)

I tried several things to accomplish this but all of them are causing problems in the long run (seemingly random access violations in certain situations).

  1. Prepare the action, then invoke a background thread and at the end of the background thread, use Synchronize to call an OnFinish event in the main thread.
  2. Prepare the action, then invoke a background thread and at the end of the background thread, use PostMessage to inform the GUI thread that the results are ready.
  3. Prepare the action, then invoke a background thread, then busy-wait (while calling Application.ProcessMessages) until the background thread is finished, then proceed with displaying the results.

I cannot come up with another alternative and none of this worked perfectly for me. What is the preferred way to do this?

3条回答
劫难
2楼-- · 2019-04-24 13:49

You can implement the pattern questioned by using OTL as demonstrated by the OTL author here

查看更多
小情绪 Triste *
3楼-- · 2019-04-24 13:53

1) Is the 'Orignal Delphi' way, forces the background thread to wait until the synchronized method has been executed and exposes the system to more deadlock-potential than I am happy with. TThread.Synchronize has been re-written at least twice. I used it once, on D3, and had problems. I looked at how it worked. I never used it again.

2) I the design I use most often. I use app-lifetime threads, (or thread pools), create inter-thread comms objects and queue them to background threads using a producer-consumer queue based on a TObjectQueue descendant. The background thread/s operate on the data/methods of the object, store results in the object and, when complete, PostMessage() the object, (cast to lParam) back to the main thread for GUI display of results in a message-handler, (cast the lParam back again). The background threads in the main GUI thread then never have to operate on the same object and never have to directly access any fields of each other.

I use a hidden window of the GUI thread, (created with RegisterWindowClass and CreateWindow), for the background threads to PostMessage to, comms object in LParam and 'target' TwinControl, (usually a TForm class), as WParam. The trivial wndproc for the hidden window just uses TwinControl.Perform() to pass on the LParam to a message-handler of the form. This is safer than PostMessaging the object directly to a TForm.handle - the handle can, unfortunately, change if the window is recreated. The hidden window never calls RecreateWindow() and so its handle never changes.

Producer-consumer queues 'out from GUI', inter-thread comms classes/objects and PostMessage() 'in to GUI' WILL work well - I've been doing it for decades.

Re-using the comms objects is fairly easy too - just create a load in a loop at startup, (preferably in an initialization section so that the comms objects outlive all forms), and push them onto a P-C queue - that's your pool. It's easier if the comms class has a private field for the pool instance - the 'releaseBackToPool' method then needs no parameters and, if there is more than one pool, ensures that the objects are always released back to their own pool.

3) Can't really improve on David Hefferman's comment. Just don't do it.

查看更多
地球回转人心会变
4楼-- · 2019-04-24 14:13

You could communicate data between threads as messages.

Thread1:

  1. allocate memory for a data structure
  2. fill it in
  3. send a message to Thread2 with the pointer to this structure (you could either use Windows messages or implement a queue, insuring its enque and dequeue methods don't have race conditions)
  4. possibly receive a response message from Thread2...

Thread2:

  1. receive the message with the pointer to the data structure from Thread1
  2. consume the data
  3. deallocate the data structure's memory
  4. possibly send a message back to Thread1 in a similar fashion (perhaps reusing the data structure, but then you don't deallocate it)

You may end up with more than 1 non-GUI thread if you want your GUI not only live, but also responding to some input, while the input that takes long time to be processed is being processed.

查看更多
登录 后发表回答