BackgroundWorker vs chủ đề nền


166

Tôi có một câu hỏi phong cách về sự lựa chọn thực hiện chủ đề nền mà tôi nên sử dụng trên một ứng dụng biểu mẫu windows. Hiện tại tôi có một BackgroundWorkerhình thức có một (while(true))vòng lặp vô hạn . Trong vòng lặp này, tôi sử dụng WaitHandle.WaitAnyđể giữ cho chủ đề báo lại cho đến khi điều gì đó quan tâm xảy ra. Một trong những sự kiện xử lý mà tôi chờ đợi là một StopThreadsự kiện "" để tôi có thể thoát ra khỏi vòng lặp. Sự kiện này được báo hiệu khi từ ghi đè của tôi Form.Dispose().

Tôi đã đọc ở đâu đó BackgroundWorkerthực sự dành cho các hoạt động mà bạn không muốn kết nối UI và có một kết thúc hữu hạn - như tải xuống một tệp hoặc xử lý một chuỗi các mục. Trong trường hợp này, "kết thúc" là không xác định và chỉ khi cửa sổ được đóng lại. Vì vậy, nó sẽ thích hợp hơn cho tôi để sử dụng một Chủ đề nền thay vì BackgroundWorkercho mục đích này?

Câu trả lời:


88

Từ sự hiểu biết của tôi về câu hỏi của bạn, bạn đang sử dụng một BackgroundWorkerChủ đề tiêu chuẩn.

Lý do tại sao BackgroundWorkerđược khuyến nghị cho những thứ mà bạn không muốn kết nối chuỗi UI là vì nó hiển thị một số sự kiện hay khi thực hiện phát triển Win Forms.

Các sự kiện muốn RunWorkerCompletedbáo hiệu khi luồng đã hoàn thành những gì cần làm và ProgressChangedsự kiện cập nhật GUI về tiến trình của luồng.

Vì vậy, nếu bạn không sử dụng những thứ này, tôi sẽ không thấy bất kỳ tác hại nào khi sử dụng một Thread tiêu chuẩn cho những gì bạn cần làm.


Một vấn đề khác mà tôi không chắc chắn là, giả sử tôi đang cố gắng loại bỏ biểu mẫu có trình xử lý nền chạy trên nó. Tôi báo hiệu tắt máy (ManualResetEvent) và đôi khi sau đó DoWork sẽ thoát ra một cách duyên dáng. Tôi có nên để biểu mẫu đi trước và Vứt bỏ mặc dù DoWork có thể mất một chút thời gian để hoàn thành, hoặc có cách nào đó (và tốt hơn) để xử lý. Tham gia vào nhân viên nền cho đến khi nó thực sự thoát ra và sau đó cho phép của hình thức tiếp tục?
freddy smith

Tôi nghĩ BackgroundWorker.IsBusy là những gì bạn đang tìm kiếm ở đó.
ParmesanCodice

1
Chỉ sử dụng CancelAsync(và kiểm tra CancellationPendingxem luồng của bạn sẽ bỏ phiếu trong các khoảng thời gian ngắn, nếu bạn muốn có một ngoại lệ được đưa ra thay vào đó, hãy sử dụng một System.Threading.Thread.Abort()ngoại lệ trong chính khối chặn, chọn mô hình phù hợp cho tình huống.
Brett Ryan

369

Một số suy nghĩ của tôi ...

  1. Sử dụng BackgroundWorker nếu bạn có một tác vụ duy nhất chạy trong nền và cần tương tác với giao diện người dùng. Nhiệm vụ sắp xếp dữ liệu và các cuộc gọi phương thức đến luồng UI được xử lý tự động thông qua mô hình dựa trên sự kiện. Tránh BackgroundWorker nếu ...
    • lắp ráp của bạn không có hoặc không tương tác trực tiếp với UI,
    • bạn cần chủ đề là một chủ đề nền trước, hoặc
    • bạn cần thao tác ưu tiên luồng.
  2. Sử dụng một ThreadPool chủ đề khi hiệu quả là mong muốn. ThreadPool giúp tránh các chi phí liên quan đến việc tạo, bắt đầu và dừng các chủ đề. Tránh sử dụng ThreadPool nếu ...
    • tác vụ chạy suốt đời trong ứng dụng của bạn,
    • bạn cần chủ đề là một chủ đề nền trước,
    • bạn cần thao tác ưu tiên luồng, hoặc
    • bạn cần các chủ đề để có một danh tính cố định (hủy bỏ, đình chỉ, khám phá).
  3. Sử dụng lớp Thread cho các tác vụ chạy dài và khi bạn yêu cầu các tính năng được cung cấp bởi một mô hình luồng chính thức, ví dụ: chọn giữa các luồng nền trước và nền, điều chỉnh mức độ ưu tiên của luồng, kiểm soát chi tiết đối với thực thi luồng, v.v.

10
Công cụ nền nằm trong cụm System.dll và không gian tên System.ComponentModel. Không có sự phụ thuộc vào Winforms.
Kugel

17
Điều đó là chính xác, nhưng BackgroundWorkerđược thiết kế để báo cáo tiến trình xử lý cho một bên quan tâm, thường liên quan đến giao diện người dùng. Tài liệu MSDN cho lớp làm cho điều này rất rõ ràng. Nếu bạn chỉ cần một tác vụ được thực hiện trong nền, hãy sử dụng một ThreadPoolluồng.
Matt Davis

5
Liên quan đến quan điểm của bạn về System.Windows.Formslắp ráp; BackgroundWorkercũng hữu ích cho các ứng dụng WPF và những ứng dụng đó có thể không có tham chiếu đến WinForms.
GiddyUpHorsey

12

Matt Davis nói khá nhiều, với những điểm bổ sung sau:

Đối với tôi, điểm khác biệt chính BackgroundWorkerlà sự sắp xếp tự động của sự kiện đã hoàn thành thông qua SynchronizationContext. Trong ngữ cảnh UI, điều này có nghĩa là sự kiện đã hoàn thành kích hoạt luồng UI và do đó có thể được sử dụng để cập nhật UI. Đây là một điểm khác biệt chính nếu bạn đang sử dụng BackgroundWorkerbối cảnh UI.

Các tác vụ được thực thi thông qua ThreadPoolkhông thể dễ dàng bị hủy (điều này bao gồm ThreadPool. QueueUserWorkItemVà các đại biểu thực thi không đồng bộ). Vì vậy, trong khi nó tránh được chi phí của spinup luồng, nếu bạn cần hủy bỏ, hãy sử dụng một BackgroundWorkerhoặc (nhiều khả năng bên ngoài UI) quay lên một luồng và giữ một tham chiếu đến nó để bạn có thể gọi Abort().


1
Chỉ ... hy vọng ứng dụng được thiết kế về một phương pháp rõ ràng để dừng một tác vụ theo luồng (Nói chung là

11

Ngoài ra, bạn đang buộc một chuỗi luồng trong suốt cuộc đời của nhân viên nền, điều này có thể gây lo ngại vì chỉ có một số hữu hạn trong số họ. Tôi sẽ nói rằng nếu bạn chỉ tạo luồng một lần cho ứng dụng của mình (và không sử dụng bất kỳ tính năng nào của trình xử lý nền) thì hãy sử dụng một luồng, thay vì luồng xử lý nền / luồng.


1
Tôi nghĩ rằng đây là một điểm tốt. Vì vậy, thông báo tôi nhận được từ điều này là sử dụng một nhân viên nền nếu bạn cần một chủ đề nền "tạm thời" trong suốt vòng đời của Biểu mẫu, tuy nhiên nếu bạn cần một chủ đề nền cho toàn bộ thời gian của biểu mẫu (có thể là vài phút, vài giờ, ngày ...) sau đó sử dụng Chủ đề thay vì BackgroundWorker để không lạm dụng mục đích của ThreadPool
freddy smith

Về: "... có thể đáng lo ngại vì chỉ có một số lượng hữu hạn", bạn có nghĩa là các ứng dụng khác trên HĐH có thể cần chúng và chia sẻ từ cùng một 'nhóm' không?
Dan W

8

Bạn biết đấy, đôi khi làm việc với BackgroundWorker dễ dàng hơn bất kể bạn đang sử dụng Windows Forms, WPF hay bất kỳ công nghệ nào. Phần gọn gàng về những kẻ này là bạn có thể xâu chuỗi mà không phải lo lắng quá nhiều về việc chủ đề của bạn đang thực thi ở đâu, điều này rất tốt cho các tác vụ đơn giản.

Trước khi sử dụng BackgroundWorkerxem xét trước nếu bạn muốn hủy một luồng (đóng ứng dụng, hủy người dùng), sau đó bạn cần quyết định xem luồng của bạn có nên kiểm tra hủy hay không nếu nó bị đẩy khi thực hiện.

BackgroundWorker.CancelAsync()sẽ thiết lập CancellationPendingđể truenhưng sẽ không làm bất cứ điều gì nhiều hơn, đó là sau đó các chủ đề trách nhiệm liên tục kiểm tra điều này, hãy nhớ rằng bạn cũng có thể kết thúc với một điều kiện chủng tộc trong cách tiếp cận này, nơi người dùng của bạn bị hủy bỏ, nhưng các chủ đề hoàn thành trước khi thử nghiệm cho CancellationPending.

Thread.Abort() mặt khác sẽ đưa ra một ngoại lệ trong quá trình thực thi luồng thực thi hủy bỏ luồng đó, bạn phải cẩn thận về những gì có thể nguy hiểm nếu ngoại lệ này đột nhiên được đưa ra trong khi thực hiện.

Threading cần xem xét rất cẩn thận cho dù nhiệm vụ là gì, để đọc thêm:

Lập trình song song trong .NET Framework Quản lý luồng thực hành tốt nhất


5

Tôi biết cách sử dụng các chủ đề trước khi tôi biết .NET, vì vậy tôi đã quen với việc sử dụng BackgroundWorkers. Matt Davis đã tóm tắt sự khác biệt với sự xuất sắc tuyệt vời, nhưng tôi sẽ nói thêm rằng việc hiểu chính xác những gì mã đang làm là khó khăn hơn và điều này có thể làm cho việc gỡ lỗi khó khăn hơn. Thật dễ dàng để nghĩ về việc tạo và tắt các chủ đề, IMO, hơn là nghĩ về việc giao công việc cho một nhóm các chủ đề.

Tôi vẫn không thể nhận xét bài đăng của người khác, vì vậy hãy tha thứ cho sự than vãn nhất thời của tôi trong việc sử dụng câu trả lời để giải quyết các vấn đề7

Thread.Abort();Thay vào đó, đừng sử dụng , báo hiệu một sự kiện và thiết kế chuỗi của bạn để kết thúc một cách duyên dáng khi được báo hiệu. Thread.Abort()làm tăng một ThreadAbortExceptionđiểm tùy ý trong thực thi của luồng, có thể làm tất cả các loại điều không vui như Màn hình mồ côi, trạng thái chia sẻ bị hỏng, v.v.
http://msdn.microsoft.com/en-us/l Library / system.threading.thread.abort.aspx


2

Nếu nó không bị hỏng - hãy sửa nó cho đến khi nó ... đùa thôi :)

Nhưng nghiêm túc, BackgroundWorker có lẽ rất giống với những gì bạn đã có, nếu bạn bắt đầu với nó ngay từ đầu có lẽ bạn sẽ tiết kiệm được một chút thời gian - nhưng tại thời điểm này tôi không thấy cần thiết. Trừ khi một cái gì đó không hoạt động, hoặc bạn nghĩ rằng mã hiện tại của bạn là khó hiểu, thì tôi sẽ gắn bó với những gì bạn có.


2

Sự khác biệt cơ bản là, như bạn đã nêu, tạo các sự kiện GUI từ BackgroundWorker. Nếu luồng không cần cập nhật hiển thị hoặc tạo sự kiện cho luồng GUI chính, thì đó có thể là một luồng đơn giản.


2

Tôi muốn chỉ ra một hành vi của lớp BackgroundWorker chưa được đề cập. Bạn có thể tạo một Chủ đề bình thường để chạy trong nền bằng cách đặt thuộc tính Thread.IsBackground.

Chủ đề nền giống hệt với chủ đề nền trước, ngoại trừ việc chủ đề nền không ngăn quá trình kết thúc. [ 1 ]

Bạn có thể kiểm tra hành vi này bằng cách gọi phương thức sau trong hàm tạo của cửa sổ biểu mẫu của bạn.

void TestBackgroundThread()
{
    var thread = new Thread((ThreadStart)delegate()
    {
        long count = 0;
        while (true)
        {
            count++;
            Debug.WriteLine("Thread loop count: " + count);
        }
    });

    // Choose one option:
    thread.IsBackground = true; // <--- This will make the thread run in background
    thread.IsBackground = false; // <--- This will delay program termination

    thread.Start();
}

Khi thuộc tính IsBackground được đặt thành true và bạn đóng cửa sổ, thì ứng dụng của bạn sẽ chấm dứt Normaly.

Nhưng khi thuộc tính IsBackground được đặt thành false (theo mặc định) và bạn đóng cửa sổ, thì cửa sổ sẽ biến mất nhưng quá trình vẫn tiếp tục chạy.

Lớp BackgroundWorker sử dụng một Thread chạy trong nền.


1

Một nhân viên nền là một lớp hoạt động trong một luồng riêng biệt, nhưng nó cung cấp chức năng bổ sung mà bạn không có được với một Chủ đề đơn giản (như xử lý báo cáo tiến trình tác vụ).

Nếu bạn không cần các tính năng bổ sung được cung cấp bởi một nhân viên nền - và có vẻ như bạn không - thì một Chủ đề sẽ phù hợp hơn.


-1

Điều khó hiểu với tôi là nhà thiết kế phòng thu trực quan chỉ cho phép bạn sử dụng BackgroundWorkers và Timers không thực sự hoạt động với dự án dịch vụ.

Nó cung cấp cho bạn các điều khiển kéo và thả gọn gàng vào dịch vụ của bạn nhưng ... thậm chí không thử triển khai nó. Sẽ không làm việc.

Dịch vụ: Chỉ sử dụng System.Timers.Timer System.Windows.Forms.Timer sẽ không hoạt động mặc dù nó có sẵn trong hộp công cụ

Dịch vụ: BackgroundWorkers sẽ không hoạt động khi nó chạy như một dịch vụ Sử dụng System.Threading.ThreadPools thay vào đó hoặc Async gọi

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.