Có trường hợp nào thích hợp sử dụng một đối tượng Thread cũ đơn giản thay vì một trong các cấu trúc mới hơn không?


112

Tôi thấy rất nhiều người trong các bài đăng trên blog và ở đây trên SO hoặc tránh hoặc khuyên chống lại việc sử dụng Threadlớp trong các phiên bản C # gần đây (và ý tôi là tất nhiên 4.0+, với việc bổ sung Task& bạn bè). Ngay cả trước đây, đã có những cuộc tranh luận về thực tế là chức năng của một luồng cũ đơn giản có thể được thay thế trong nhiều trường hợp bởi ThreadPoollớp.

Ngoài ra, các cơ chế chuyên biệt khác đang tiếp tục làm cho Threadlớp kém hấp dẫn hơn, chẳng hạn như Timerthay thế tổ hợp Thread+ xấu xí Sleep, trong khi đối với GUI chúng ta có BackgroundWorker, v.v.

Tuy nhiên, Threaddường như vẫn là một khái niệm rất quen thuộc đối với một số người (bao gồm cả tôi), những người khi đối mặt với một nhiệm vụ liên quan đến một số loại thực hiện song song, chuyển trực tiếp sang sử dụng Threadlớp cũ tốt . Gần đây, tôi đã tự hỏi liệu đã đến lúc phải sửa đổi cách làm của mình chưa.

Vì vậy, câu hỏi của tôi là, có trường hợp nào cần thiết hoặc hữu ích để sử dụng một Threadđối tượng cũ đơn thuần thay vì một trong các cấu trúc trên không?


4
Không phải nitpick (hoặc có thể ... :)), nhưng đó là .NET (tức là không giới hạn ở C #).
MetalMikester

11
Cho đến nay đại diện trung bình cho một người dùng trả lời câu hỏi này là 64.5k. Khá một diễn ấn tượng
zzzzBov

1
@zzzzBov: Tôi đã suy nghĩ về việc đăng câu trả lời của riêng mình nhưng bây giờ tôi không thể vì sợ hạ mức trung bình :)
Brian Gideon

@BrianGideon, tôi không hiểu tại sao điều đó lại ngăn bạn hoặc bất kỳ ai khác trả lời câu hỏi này.
zzzzBov

@Brian Gideon: Bằng mọi cách, hãy đăng. :)
Tudor

Câu trả lời:


107

Lớp Thread không thể bị lỗi thời vì rõ ràng nó là chi tiết triển khai của tất cả các mẫu khác mà bạn đề cập.

Nhưng đó không thực sự là câu hỏi của bạn; câu hỏi của bạn là

Có trường hợp nào cần thiết hoặc hữu ích khi sử dụng một đối tượng Thread cũ đơn thuần thay vì một trong các cấu trúc ở trên không?

Chắc chắn rồi. Chính xác là những trường hợp mà một trong những cấu trúc cấp cao hơn không đáp ứng được nhu cầu của bạn.

Lời khuyên của tôi là nếu bạn thấy mình ở trong tình huống mà các công cụ trừu tượng cao hơn hiện có không đáp ứng được nhu cầu của bạn và bạn muốn triển khai một giải pháp bằng cách sử dụng các chuỗi, thì bạn nên xác định phần trừu tượng còn thiếu mà bạn thực sự cần và sau đó triển khai phần trừu tượng đó bằng cách sử dụng các chủ đề , và sau đó sử dụng phần trừu tượng .


33
Mọi lời khuyên tốt. Nhưng bạn có một ví dụ trong đó một đối tượng luồng cũ đơn giản có thể thích hợp hơn một trong những cấu trúc mới hơn không?
Robert Harvey

Gỡ lỗi nhiều luồng với các vấn đề về thời gian để tải một số mã đôi khi có thể được thực hiện dễ dàng hơn rất nhiều, sau đó sử dụng các cấu trúc cấp 'cao hơn' khác. Và dù sao thì bạn cũng cần có kiến ​​thức cơ bản về cách làm việc và sử dụng Threads.
CodingBarfield

Cảm ơn vì câu trả lời, Eric. Thực sự rất dễ quên, dưới sự tấn công gần đây về các tính năng luồng mới, rằng đôi khi luồng cũ vẫn cần thiết. Chà, tôi đoán dù sao thì kiến ​​thức về kim loại trần cũng hữu ích.
Tudor

15
@RobertHarvey: Chắc chắn rồi. Ví dụ: nếu bạn gặp tình huống "nhà sản xuất / người tiêu dùng" cổ điển, trong đó bạn muốn có một luồng dành riêng để lấy nội dung ra khỏi hàng đợi công việc và một luồng khác dành riêng để đưa nội dung vào hàng đợi công việc. Cả hai vòng lặp mãi mãi; chúng không liên kết tốt với các nhiệm vụ mà bạn làm trong một thời gian và sau đó nhận được kết quả. Bạn không cần một nhóm chủ đề vì chỉ có hai chủ đề. Và bạn có thể khéo léo sử dụng ổ khóa và tín hiệu để đảm bảo hàng đợi được giữ an toàn mà không chặn luồng khác trong một thời gian dài.
Eric Lippert

@Eric: Không phải TPL Dataflow đã cung cấp sự trừu tượng như vậy sao?
Allon Guralnek

52

Chủ đề là một khối xây dựng cơ bản cho một số thứ nhất định (cụ thể là song song và không đồng bộ) và do đó không nên lấy đi. Tuy nhiên , đối với hầu hết mọi người và hầu hết các trường hợp sử dụng, có những thứ thích hợp hơn để sử dụng mà bạn đã đề cập, chẳng hạn như nhóm luồng (cung cấp một cách tốt để xử lý nhiều công việc nhỏ song song mà không làm quá tải máy bằng cách tạo 2000 luồng cùng một lúc), BackgroundWorker( gói gọn các sự kiện hữu ích cho một phần công việc cần thiết).

Nhưng chỉ vì trong nhiều trường hợp, chúng thích hợp hơn vì chúng bảo vệ lập trình viên khỏi việc sáng tạo lại bánh xe một cách không cần thiết, mắc những sai lầm ngớ ngẩn và những điều tương tự, điều đó không có nghĩa là lớp Thread đã lỗi thời. Nó vẫn được sử dụng bởi các phần tóm tắt có tên ở trên và bạn vẫn sẽ cần nó nếu bạn cần kiểm soát chi tiết đối với các luồng không được bao phủ bởi các lớp đặc biệt hơn.

Tương tự như vậy, .NETkhông cấm sử dụng mảng, mặc dù List<T>phù hợp hơn với nhiều trường hợp mọi người sử dụng mảng. Đơn giản vì bạn có thể vẫn muốn xây dựng những thứ không được bao phủ bởi lib tiêu chuẩn.


6
Chỉ vì hộp số tự động hoạt động 99% thời gian, không có nghĩa là không có giá trị trong hộp số tay, thậm chí bỏ qua sở thích của người lái.
Guvante

4
Tôi không rành về các thành ngữ lái xe vì tôi là người đi xe đạp và sử dụng phương tiện công cộng. Nhưng hộp số tay không phải là trung tâm của hộp số tự động - nó không phải là một bàn tay cơ học di chuyển một thanh. Nó không thực sự là một sự trừu tượng so với cái khác, theo như tôi thấy.
Joey

2
Bạn có thể thay thế từ "chủ đề" với "mã không được quản lý" trong đoạn thứ hai và nó vẫn sẽ gọi đúng sự thật
Conrad Frix

1
@Joey: Ồ, tôi nghĩ bạn muốn nói đến thứ lỗi thời, giá trị của việc kiểm soát chi tiết tốt, với cái giá phải trả là khả năng sử dụng, mặc dù hầu hết sẽ không bao giờ cần đến nó. Về mặt kỹ thuật, phần thú vị của hộp số sàn là bộ ly hợp.
Guvante

3
Nếu các bạn thực sự muốn đi với ẩn dụ truyền, hầu hết được truyền tuần tự (ví dụ như truyền SMG của BMW) được , trên thực tế, hộp số tay với một máy tính hoạt động ly hợp và chọn thiết bị. Chúng không phải là hệ thống bánh răng hành tinh như các loại tự động thông thường.
Adam Robinson

13

TaskThreadlà những trừu tượng khác nhau. Nếu bạn muốn mô hình một luồng, Threadclass vẫn là lựa chọn thích hợp nhất. Ví dụ: nếu bạn cần tương tác với luồng hiện tại, tôi không thấy bất kỳ loại nào tốt hơn cho việc này.

Tuy nhiên, như bạn đã chỉ ra .NET đã thêm một số phần tóm tắt chuyên dụng được ưu tiên hơn Threadtrong nhiều trường hợp.


9

Các Threadlớp học không phải là lỗi thời, nó vẫn còn hữu ích trong trường hợp đặc biệt.

Ở nơi tôi làm việc, chúng tôi đã viết một 'bộ xử lý nền' như một phần của hệ thống quản lý nội dung: một dịch vụ Windows giám sát các thư mục, địa chỉ e-mail và nguồn cấp dữ liệu RSS và mỗi khi một cái gì đó mới xuất hiện sẽ thực hiện một tác vụ trên đó - thường là để nhập dữ liệu.

Nỗ lực sử dụng nhóm luồng cho việc này đã không hoạt động: nó cố gắng thực thi quá nhiều nội dung cùng một lúc và làm thùng rác các đĩa, vì vậy chúng tôi đã triển khai hệ thống thực thi và thăm dò của riêng mình bằng cách sử dụng trực tiếp Threadlớp.


Tôi không biết liệu sử dụng Thread trực tiếp ở đây có phải là giải pháp phù hợp ở đây hay không, nhưng +1 để thực sự đưa ra một ví dụ mà việc truy cập Thread là bắt buộc.
Oddthinking ngày

6

Các tùy chọn mới làm cho việc sử dụng và quản lý trực tiếp các luồng (đắt tiền) ít thường xuyên hơn.

những người mà khi đối mặt với một nhiệm vụ liên quan đến một số loại thực thi song song, hãy chuyển trực tiếp sang sử dụng lớp Thread cũ tốt.

Đây là một cách làm song song rất tốn kém và tương đối phức tạp.

Lưu ý rằng chi phí quan trọng nhất: Bạn không thể sử dụng toàn bộ chuỗi để thực hiện một công việc nhỏ, nó sẽ phản tác dụng. ThreadPool chống lại các chi phí, lớp Task phức tạp (ngoại lệ, chờ đợi và hủy bỏ).


5

Để trả lời câu hỏi " có bất kỳ trường hợp nào cần thiết hoặc hữu ích khi sử dụng một đối tượng Thread cũ thuần túy không ", tôi muốn nói rằng một Thread cũ đơn giản là hữu ích (nhưng không cần thiết) khi bạn có một quá trình chạy dài mà bạn đã thắng ' không bao giờ tương tác với từ một chủ đề khác.

Ví dụ: nếu bạn đang viết một ứng dụng đăng ký nhận tin nhắn từ một số loại hàng đợi tin nhắn và ứng dụng của bạn sẽ làm nhiều việc hơn là chỉ xử lý những tin nhắn đó thì sẽ hữu ích khi sử dụng một Chuỗi vì chuỗi sẽ khép kín (tức là bạn không chờ đợi nó hoàn thành), và nó không tồn tại trong thời gian ngắn. Sử dụng lớp ThreadPool nhiều hơn để xếp hàng đợi một loạt các mục công việc tồn tại trong thời gian ngắn và cho phép lớp ThreadPool quản lý xử lý hiệu quả từng mục khi có một Chủ đề mới. Các tác vụ có thể được sử dụng khi bạn sử dụng Thread trực tiếp, nhưng trong trường hợp trên, tôi không nghĩ họ sẽ mua cho bạn nhiều. Chúng giúp bạn tương tác với chuỗi dễ dàng hơn (điều mà trường hợp trên không làm được '


Tôi đồng ý rằng hầu hết những thứ thú vị về tính song song mới được thiết kế xoay quanh các nhiệm vụ chi tiết nhỏ ngắn hạn và các luồng chạy dài vẫn nên được thực hiện với System.IO.Threading.Thread.
Ben Voigt

4

Có thể không phải là câu trả lời mà bạn mong đợi, nhưng tôi sử dụng Chủ đề mọi lúc khi viết mã dựa trên .NET Micro Framework. MF được cắt giảm khá nhiều và không bao gồm các yếu tố trừu tượng ở mức cao hơn và lớp Thread siêu linh hoạt khi bạn cần có được bit cuối cùng của hiệu suất từ ​​CPU có MHz thấp.


Này, đó thực sự là một ví dụ khá hay! Cảm ơn. Tôi chưa nghĩ về các khuôn khổ không hỗ trợ các tính năng mới.
Tudor

3

Bạn có thể so sánh lớp Thread với ADO.NET. Nó không phải là công cụ được khuyến nghị để hoàn thành công việc, nhưng nó không hề lỗi thời. Các công cụ khác được xây dựng dựa trên nó để dễ dàng công việc.

Không sai khi sử dụng lớp Thread thay cho những thứ khác, đặc biệt nếu những thứ đó không cung cấp một chức năng mà bạn cần.


3

Nó chắc chắn không phải là lỗi thời.

Vấn đề với các ứng dụng đa luồng là chúng rất khó hoạt động đúng (thường là hành vi không xác định, đầu vào, đầu ra và trạng thái bên trong cũng rất quan trọng), vì vậy một lập trình viên nên đẩy càng nhiều công việc vào framework / công cụ càng tốt. Tóm tắt nó đi. Nhưng, kẻ thù truyền kiếp của trừu tượng là hiệu suất.

Vì vậy, câu hỏi của tôi là, có bất kỳ trường hợp nào cần thiết hoặc hữu ích để sử dụng một đối tượng Thread cũ đơn thuần thay vì một trong các cấu trúc trên không?

Tôi chỉ sử dụng Chủ đề và khóa nếu có vấn đề nghiêm trọng về hiệu suất, mục tiêu hiệu suất cao.


3

Tôi luôn sử dụng lớp Thread khi tôi cần giữ số lượng và kiểm soát các chủ đề mà tôi đã quay. Tôi nhận ra rằng tôi có thể sử dụng threadpool để lưu giữ tất cả công việc còn tồn đọng của mình, nhưng tôi chưa bao giờ tìm ra cách tốt để theo dõi lượng công việc hiện đang được hoàn thành hoặc trạng thái ra sao.

Thay vào đó, tôi tạo một bộ sưu tập và đặt các chủ đề vào chúng sau khi tôi xoay chúng lên - điều cuối cùng mà một chủ đề làm là xóa chính nó khỏi bộ sưu tập. Bằng cách đó, tôi luôn có thể biết có bao nhiêu luồng đang chạy và tôi có thể sử dụng bộ sưu tập để hỏi từng luồng đang làm gì. Nếu có trường hợp tôi cần phải giết tất cả chúng, thông thường bạn phải đặt một số loại cờ "Hủy bỏ" trong ứng dụng của mình, đợi mọi chuỗi tự nhận thấy điều đó và tự kết thúc - trong trường hợp của tôi, tôi có thể xem qua bộ sưu tập và đưa ra một Thread.Abort cho từng cái một.

Trong trường hợp đó, tôi chưa tìm ra cách tốt hơn để làm việc trực tiếp với lớp Thread. Như Eric Lippert đã đề cập, những cái khác chỉ là những phần trừu tượng cấp cao hơn và rất thích hợp để làm việc với các lớp cấp thấp hơn khi các triển khai cấp cao có sẵn không đáp ứng nhu cầu của bạn. Cũng giống như đôi khi bạn cần thực hiện lệnh gọi Win32 API khi .NET không giải quyết nhu cầu chính xác của bạn, sẽ luôn có trường hợp lớp Thread là lựa chọn tốt nhất mặc dù có "những tiến bộ" gần đây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.