Edit:
Dường như rất nhiều các chi tiết thực hiện đã thay đổi kể từ Delphi 4 và 5 (các phiên bản Delphi tôi vẫn đang sử dụng cho hầu hết các công việc của tôi), và Allen Bauer đã comment sau :
Kể từ D6, TThread không sử dụng SendMessage nữa. Nó sử dụng một hàng đợi công việc an toàn thread nơi "công việc" dành cho chủ đề chính được đặt. Một thông báo được đăng lên luồng chính để chỉ ra rằng công việc có sẵn và các chuỗi nền sẽ chặn trên một sự kiện. Khi vòng lặp tin nhắn chính sắp ngừng hoạt động, nó sẽ gọi "CheckSynchronize" để xem có công việc nào đang chờ hay không. Nếu vậy, nó xử lý nó. Khi một mục công việc được hoàn thành, sự kiện mà chủ đề nền bị chặn được thiết lập để cho biết hoàn thành. Được giới thiệu trong khung thời gian D2006, phương thức TThread.Queue đã được thêm vào không chặn.
Cảm ơn bạn đã sửa. Vì vậy, lấy các chi tiết trong câu trả lời ban đầu với một hạt muối.
Nhưng điều này thực sự không ảnh hưởng đến các điểm cốt lõi. Tôi vẫn duy trì rằng toàn bộ ý tưởng của Synchronize()
là thiếu sót nghiêm trọng, và điều này sẽ được rõ ràng thời điểm một cố gắng để giữ cho một số lõi của một máy hiện đại chiếm đóng. Đừng "đồng bộ hóa" chủ đề của bạn, hãy để chúng hoạt động cho đến khi chúng được hoàn thành. Cố gắng giảm thiểu tất cả các phụ thuộc giữa chúng. Đặc biệt khi cập nhật GUI có hoàn toàn không lý do để đợi quá trình này hoàn tất. Cho dù Synchronize()
sử dụng SendMessage()
hoặc PostMessage()
, khối đường kết quả là như nhau.
gì bạn trình bày ở đây không phải là một sự thay thế ở tất cả, như Synchronize()
sử dụng SendMessage()
nội bộ. Vì vậy, nó là nhiều hơn một câu hỏi mà vũ khí bạn muốn sử dụng để bắn mình vào chân với.
Synchronize()
đã đi cùng với chúng tôi kể từ khi giới thiệu TThread
trong VCL Delphi 2, thật đáng tiếc vì đây là một trong những tính năng thiết kế lớn hơn trong VCL.
Tính năng này hoạt động như thế nào? Nó sử dụng một cuộc gọi SendMessage()
đến một cửa sổ đã được tạo trong chủ đề chính, và thiết lập các tham số thông báo để truyền địa chỉ của một phương thức đối tượng parameterless được gọi. Vì các thông điệp Windows sẽ chỉ được xử lý trong luồng tạo cửa sổ đích và chạy vòng lặp tin nhắn của nó, nó sẽ tạm dừng luồng, xử lý thông điệp trong ngữ cảnh của chuỗi VCL chính, gọi phương thức và tiếp tục chỉ sau khi phương thức đã thực hiện xong.
Vì vậy, có gì sai với nó (và điều gì tương tự sai khi sử dụng trực tiếp SendMessage()
)? Một số điều:
- Buộc bất kỳ luồng nào để thực thi mã trong ngữ cảnh của một luồng khác buộc hai công tắc ngữ cảnh luồng, không cần phải đốt chu kỳ CPU.
- Trong khi chuỗi VCL xử lý tin nhắn để gọi phương thức được đồng bộ hóa, nó không thể xử lý bất kỳ tin nhắn nào khác.
- Khi nhiều chủ đề sử dụng phương pháp này, họ sẽ tất cả chặn và đợi
Synchronize()
hoặc SendMessage()
để trả lại. Điều này tạo ra một nút cổ chai khổng lồ.
- Có một bế tắc đang chờ xảy ra.Nếu chuỗi gọi
Synchronize()
hoặc SendMessage()
trong khi đang giữ một đối tượng đồng bộ hóa và chuỗi VCL trong khi xử lý thư cần có cùng một đối tượng đồng bộ hóa, ứng dụng sẽ khóa. Cũng có thể nói về các cuộc gọi API chờ xử lý luồng - bằng cách sử dụng WaitForSingleObject()
hoặc WaitForMultipleObjects()
mà không có phương tiện xử lý tin nhắn sẽ gây ra bế tắc nếu chuỗi cần những cách này để "đồng bộ hóa" với chuỗi khác.
Vì vậy, những gì nên sử dụng? Một số tùy chọn, tôi sẽ mô tả một số:
Sử dụng PostMessage()
thay vì SendMessage()
(hoặc PostThreadMessage()
nếu hai bài đều không phải là chủ đề VCL). Điều quan trọng là không sử dụng bất kỳ dữ liệu nào trong các thông số thư sẽ không còn hợp lệ khi thư đến, vì chuỗi gửi và nhận không được đồng bộ, vì vậy một số phương tiện khác phải được sử dụng để đảm bảo rằng bất kỳ chuỗi nào , tham chiếu đối tượng hoặc đoạn bộ nhớ vẫn hợp lệ khi thông báo được xử lý, mặc dù chuỗi gửi có thể không còn tồn tại nữa.
Tạo cấu trúc dữ liệu an toàn chủ đề, đặt dữ liệu cho chúng từ chuỗi công việc của bạn và tiêu thụ chúng từ chuỗi chính. Chỉ sử dụng PostMessage()
để cảnh báo chuỗi VCL rằng dữ liệu mới đã được xử lý nhưng không đăng thông báo mỗi lần. Nếu bạn có một dòng dữ liệu liên tục, bạn thậm chí có thể có cuộc thăm dò chủ đề VCL cho dữ liệu (có thể bằng cách sử dụng bộ hẹn giờ), nhưng đây chỉ là phiên bản của người nghèo.
Không sử dụng các công cụ cấp thấp chút nào nữa. Nếu bạn ít nhất là trên Delphi 2007, hãy tải xuống OmniThreadLibrary và bắt đầu suy nghĩ về nhiệm vụ chứ không phải chủ đề. Thư viện này có rất nhiều cơ sở để trao đổi dữ liệu giữa các chủ đề và đồng bộ hóa. Nó cũng có thực hiện thread pool, là một điều tốt - bao nhiêu luồng bạn nên sử dụng không chỉ phụ thuộc vào ứng dụng mà còn trên phần cứng mà nó đang chạy, vì vậy nhiều quyết định có thể được thực hiện chỉ khi chạy. OTL sẽ cho phép bạn chạy các tác vụ trên một luồng thread thread, vì vậy hệ thống có thể điều chỉnh số lượng các luồng đồng thời khi chạy.
Edit:
On đọc lại tôi nhận ra rằng bạn không có ý định sử dụng SendMessage()
nhưng PostMessage()
- tốt, một số trên không áp dụng sau đó, nhưng tôi sẽ để nó tại chỗ. Tuy nhiên, có một số điểm hơn trong câu hỏi của bạn tôi muốn giải quyết:
Với lên đến 16 luồng chạy cùng một lúc (và hầu hết các xử lý các chủ đề con của mất từ < 1 giây để ~ 10 giây) sẽ Messages Window là một thiết kế tốt hơn?
Nếu bạn đăng tin nhắn từ mỗi chuỗi một lần mỗi giây hoặc thậm chí lâu hơn, thì thiết kế sẽ ổn. Những gì bạn không nên làm là đăng hàng trăm hoặc nhiều tin nhắn trên mỗi luồng mỗi giây, bởi vì hàng đợi tin nhắn Windows có độ dài hữu hạn và tin nhắn tùy chỉnh không được can thiệp vào quá trình xử lý thông thường quá nhiều (chương trình của bạn sẽ bắt đầu xuất hiện không phản hồi).
nơi các bài viết chủ đề con một thông điệp cửa sổ (bao gồm một kỷ lục của một số chuỗi)
Một thông điệp cửa sổ có thể không chứa một kỷ lục.Nó mang hai tham số, một trong các loại WPARAM
, loại khác là loại LPARAM
. Bạn chỉ có thể truyền một con trỏ tới một bản ghi như vậy với một trong các loại này, vì vậy toàn bộ thời gian của bản ghi cần được quản lý bằng cách nào đó. Nếu bạn tự động phân bổ nó, bạn cũng cần phải giải phóng nó, dễ bị lỗi. Nếu bạn chuyển một con trỏ tới một bản ghi trên ngăn xếp hoặc đến một trường đối tượng, bạn cần đảm bảo rằng nó vẫn hợp lệ khi thông báo được xử lý, điều này sẽ khó hơn đối với các tin nhắn được gửi hơn là các tin nhắn đã gửi.
bạn có đề xuất bao gồm mã nơi tôi đăng lên lưới trong khối TCriticalSection (nhập và rời) không? Hoặc tôi sẽ không cần phải lo lắng về an toàn luồng vì tôi đang viết vào lưới trong chuỗi chính (mặc dù trong chức năng của trình xử lý tin nhắn cửa sổ)?
Không cần phải làm điều này, như cuộc gọi PostMessage()
sẽ trở lại ngay lập tức, vì vậy không đồng bộ là cần thiết vào thời điểm này. Bạn chắc chắn sẽ cần phải lo lắng về an toàn luồng, rất tiếc bạn không thể biết khi. Bạn phải đảm bảo rằng quyền truy cập vào dữ liệu an toàn theo luồng, bằng cách luôn luôn khóa dữ liệu để truy cập, bằng cách sử dụng đối tượng đồng bộ hóa. Không thực sự là một cách để đạt được điều đó cho các bản ghi, dữ liệu luôn có thể được truy cập trực tiếp.
"Tạo cấu trúc dữ liệu an toàn chủ đề" - đây là những gì tôi làm. Các chủ đề lưu trữ thông tin của họ ở đó và giao diện người dùng truy xuất thông tin đó trong bộ hẹn giờ hoặc tương tự. Điều này ngăn chặn bất kỳ chủ đề nào được giữ bởi giao diện người dùng. – mj2008
Câu trả lời thú vị! Cảm ơn bạn đã giải thích chi tiết! – Mick
Kể từ D6, TThread không sử dụng SendMessage nữa. Nó sử dụng một hàng đợi công việc an toàn thread nơi "công việc" dành cho chủ đề chính được đặt. Một thông báo được * đăng * vào chuỗi chính để cho biết rằng công việc có sẵn và chuỗi nền sẽ chặn trên một sự kiện. Khi vòng lặp tin nhắn chính sắp ngừng hoạt động, nó sẽ gọi "CheckSynchronize" để xem có công việc nào đang chờ hay không. Nếu vậy, nó xử lý nó. Khi một mục công việc được hoàn thành, sự kiện mà chủ đề nền bị chặn được thiết lập để cho biết hoàn thành. Được giới thiệu trong khung thời gian D2006, phương thức TThread.Queue đã được thêm vào không chặn. –