Sự khác biệt giữa nhiệm vụ và chủ đề là gì?


378

Trong C # 4.0, chúng ta có Tasktrong không gian tên System.Threading.T Nhiệm vụ . Sự khác biệt thực sự giữa ThreadTask. Tôi đã thực hiện một số chương trình mẫu (trợ giúp lấy từ MSDN) cho mục đích học tập của riêng tôi

Parallel.Invoke 
Parallel.For 
Parallel.ForEach 

nhưng có nhiều nghi ngờ vì ý tưởng không quá rõ ràng.

Ban đầu tôi đã tìm kiếm trong Stackoverflow cho một loại câu hỏi tương tự nhưng có thể với tiêu đề câu hỏi này, tôi không thể có được câu hỏi tương tự. Nếu bất cứ ai biết về loại câu hỏi tương tự được đăng ở đây sớm hơn, vui lòng cung cấp cho các tham chiếu của liên kết.


8
chủ đề chạy nhiệm vụ
pm100

Câu trả lời:


314

Một nhiệm vụ là một cái gì đó bạn muốn thực hiện.

Một chủ đề là một trong nhiều công nhân có thể thực hiện nhiệm vụ đó.

Trong các điều khoản .NET 4.0, một Tác vụ thể hiện một hoạt động không đồng bộ. (Các) luồng được sử dụng để hoàn thành thao tác đó bằng cách chia công việc thành các khối và gán cho các luồng riêng biệt.


Bạn có thể cung cấp một ví dụ thô sơ về các luồng làm việc để hoàn thành một nhiệm vụ? Tôi không biết các chủ đề đang làm việc độc lập với nhau hay họ thực hiện một số tính toán làm việc theo nhóm ?
penum

Cả hai kịch bản đều có thể: trong một tình huống tối ưu, các luồng thực hiện công việc độc lập mà không cần phải đồng bộ hóa với các luồng khác. Trong thực tế, khóa được sử dụng để phối hợp các chủ đề.
Mitch Wheat

451

Trong thuật ngữ khoa học máy tính, a Tasklà một tương lai hoặc một lời hứa . (Một số người sử dụng hai thuật ngữ này một cách đồng bộ, một số sử dụng chúng khác nhau, không ai có thể đồng ý về một định nghĩa chính xác .) Về cơ bản, một Task<T>"lời hứa" sẽ trả lại cho bạn một T, nhưng không phải bây giờ, tôi hơi bận, tại sao không bạn quay lại sau?

A Threadlà một cách để thực hiện lời hứa đó. Nhưng không phải mọi Tasknhu cầu một thương hiệu mới Thread. (Trong thực tế, việc tạo một luồng thường không mong muốn, bởi vì làm như vậy tốn kém hơn nhiều so với việc sử dụng lại một luồng hiện có từ luồng đó. Thêm vào đó trong một khoảnh khắc.) cơ sở dữ liệu hoặc mạng, sau đó không cần một luồng để ngồi và chờ dữ liệu khi nó có thể phục vụ các yêu cầu khác. Thay vào đó, Taskcó thể đăng ký gọi lại để nhận (các) giá trị khi chúng sẵn sàng.

Đặc biệt, Taskkhông không nói lý do tại sao nó là nó mất một thời gian dài như vậy để trả về giá trị. Có thể mất nhiều thời gian để tính toán hoặc có thể mất nhiều thời gian để tìm nạp. Chỉ trong trường hợp trước, bạn sẽ sử dụng a Threadđể chạy a Task. (Trong .NET, các luồng rất đắt, vì vậy bạn thường muốn tránh chúng càng nhiều càng tốt và thực sự chỉ sử dụng chúng nếu bạn muốn chạy nhiều tính toán nặng trên nhiều CPU. Ví dụ, trong Windows, một luồng nặng 12 KiByte ( Tôi nghĩ), trong Linux, một luồng chỉ nặng bằng 4 KiByte, trong Erlang / BEAM thậm chí chỉ 400 Byte. Trong .NET, đó là 1 MiByte!)


29
Điều thú vị là trong các bản phát hành xem trước của TPL (Thư viện song song nhiệm vụ) có Nhiệm vụ và tương lai <T>. Tương lai <T> sau đó được đổi tên thành Nhiệm vụ <T>. :)
Lee Campbell

23
Làm thế nào bạn tính được 1 MB cho .NET?
dvallejo

5
@DanVallejo: Con số đó đã được đề cập trong một cuộc phỏng vấn với nhóm thiết kế TPL. Tôi không thể nói cho bạn biết ai đã nói nó hay cuộc phỏng vấn nào, tôi đã xem những năm trước.
Jörg W Mittag

9
@RIPUNJAYTRIPATHI Chắc chắn, nhưng nó không cần phải là một chủ đề khác , nó có thể là chủ đề yêu cầu công việc ở nơi đầu tiên.
Chris Pitman

7
.NET chỉ sử dụng các luồng Windows trên Windows, vì vậy kích thước là như nhau - theo mặc định, thường là 1 MiB bộ nhớ ảo cho cả hai. Bộ nhớ vật lý được sử dụng khi cần thiết trong các đoạn có kích thước trang (thường là 64 kiB), giống với mã gốc. Kích thước ngăn xếp luồng tối thiểu phụ thuộc vào HĐH - ví dụ 256 kiB cho Vista. Trên x86 Linux, mặc định thường là 2 MiB - một lần nữa, được phân bổ theo các phần có kích thước trang. (đơn giản hóa) Erlang chỉ sử dụng một luồng hệ thống cho mỗi tiến trình, 400 byte đó đề cập đến một cái gì đó tương tự như .NET Task.
Luaan

39

Chủ đề

Thứ kim loại trần trụi, có lẽ bạn không cần sử dụng nó, có lẽ bạn có thể sử dụng một LongRunningtác vụ và nhận các lợi ích từ Thư viện song song TPL, bao gồm trong .NET Framework 4 (tháng hai, 2002) trở lên (cũng là .NET Cốt lõi).

Nhiệm vụ

Trừu tượng trên Chủ đề. Nó sử dụng nhóm luồng (trừ khi bạn chỉ định tác vụ là một LongRunningthao tác, nếu vậy, một luồng mới được tạo dưới mui xe cho bạn).

Chủ đề hồ bơi

Như tên cho thấy: một nhóm các chủ đề. Là .NET framework xử lý một số lượng hạn chế của các chủ đề cho bạn. Tại sao? Bởi vì việc mở 100 luồng để thực thi các hoạt động CPU đắt tiền trên Bộ xử lý chỉ với 8 lõi chắc chắn không phải là ý kiến ​​hay. Khung công tác sẽ duy trì nhóm này cho bạn, sử dụng lại các luồng (không tạo / giết chúng trong mỗi thao tác) và thực hiện song song một số trong số chúng, theo cách mà CPU của bạn sẽ không bị cháy.

OK, nhưng khi nào nên sử dụng từng cái?

Trong sơ yếu lý lịch: luôn luôn sử dụng các nhiệm vụ.

Nhiệm vụ là một sự trừu tượng, vì vậy nó dễ sử dụng hơn rất nhiều. Tôi khuyên bạn luôn cố gắng sử dụng các tác vụ và nếu bạn gặp phải một số vấn đề khiến bạn cần phải tự xử lý một luồng (có thể là 1% thời gian) thì hãy sử dụng các luồng.

NHƯNG lưu ý rằng:

  • Giới hạn I / O : Đối với các hoạt động bị ràng buộc I / O (các cuộc gọi cơ sở dữ liệu, đọc / ghi tệp, các cuộc gọi API, v.v.) tránh sử dụng các tác vụ thông thường, hãy sử dụng LongRunningcác tác vụ ( hoặc các luồng nếu bạn cần ). Bởi vì việc sử dụng các tác vụ sẽ dẫn bạn đến một nhóm luồng với một vài luồng bận rộn và rất nhiều tác vụ khác đang chờ đến lượt để lấy nhóm.
  • Giới hạn CPU : Đối với các hoạt động bị ràng buộc của CPU, chỉ cần sử dụng các tác vụ thông thường (bên trong sẽ sử dụng nhóm luồng) và hài lòng.

điều chỉnh nhẹ, một chủ đề không phải là "thứ kim loại trần". nó được HĐH triển khai, hầu hết các triển khai đều chuyển tiếp trên các tính năng của CPU và CS, nhưng chúng không được phần cứng triển khai.
Tomer W

7

Bạn có thể sử dụng Taskđể chỉ định những gì bạn muốn làm sau đó đính kèm Taskvới a Thread. do đó, nó Tasksẽ được thực thi trong đó mới được thực hiện Threadchứ không phải trên luồng GUI.

Sử dụng Taskvới TaskFactory.StartNew(Action action). Ở đây bạn thực thi một ủy nhiệm vì vậy nếu bạn không sử dụng bất kỳ luồng nào thì nó sẽ được thực thi trong cùng một luồng (luồng GUI). Nếu bạn đề cập đến một chủ đề, bạn có thể thực hiện điều này Tasktrong một chủ đề khác. Đây là một công việc không cần thiết vì bạn có thể trực tiếp thực hiện ủy nhiệm hoặc đính kèm ủy nhiệm đó vào một luồng và thực thi ủy nhiệm đó trong luồng đó. Vì vậy, không sử dụng nó. nó không cần thiết Nếu bạn có ý định tối ưu hóa phần mềm của mình thì đây là một ứng cử viên tốt cần được loại bỏ.

** Xin lưu ý rằng đó Actionlà một delegate.


6

Ngoài những điểm trên, sẽ rất tốt nếu biết rằng:

  1. Một tác vụ theo mặc định là một tác vụ nền. Bạn không thể có một nhiệm vụ tiền cảnh. Mặt khác, một luồng có thể là nền hoặc tiền cảnh (Sử dụng thuộc tính IsBackground để thay đổi hành vi).
  2. Các tác vụ được tạo trong nhóm luồng tái chế các luồng giúp tiết kiệm tài nguyên. Vì vậy, trong hầu hết các trường hợp, nhiệm vụ nên là sự lựa chọn mặc định của bạn.
  3. Nếu các thao tác nhanh, tốt hơn là sử dụng một tác vụ thay vì luồng. Đối với các hoạt động chạy dài, các tác vụ không cung cấp nhiều lợi thế hơn các luồng.

4

Tôi thường sử dụng Taskđể tương tác với Winforms và công cụ nền đơn giản để làm cho nó không bị đóng băng UI. đây là một ví dụ khi tôi thích sử dụngTask

private async void buttonDownload_Click(object sender, EventArgs e)
{
    buttonDownload.Enabled = false;
    await Task.Run(() => {
        using (var client = new WebClient())
        {
            client.DownloadFile("http://example.com/file.mpeg", "file.mpeg");
        }
    })
    buttonDownload.Enabled = true;
}

VS

private void buttonDownload_Click(object sender, EventArgs e)
{
    buttonDownload.Enabled = false;
    Thread t = new Thread(() =>
    {
        using (var client = new WebClient())
        {
            client.DownloadFile("http://example.com/file.mpeg", "file.mpeg");
        }
        this.Invoke((MethodInvoker)delegate()
        {
            buttonDownload.Enabled = true;
        });
    });
    t.IsBackground = true;
    t.Start();
}

sự khác biệt là bạn không cần sử dụng MethodInvokervà mã ngắn hơn.


4

Nhiệm vụ giống như một hoạt động mà bạn muốn thực hiện, Thread giúp quản lý các hoạt động đó thông qua nhiều nút quá trình. tác vụ là một tùy chọn gọn nhẹ vì Threading có thể dẫn đến việc quản lý mã phức tạp
mà tôi sẽ đề nghị đọc từ MSDN (Tốt nhất thế giới) luôn

Nhiệm vụ

Chủ đề


3

Một tác vụ có thể được coi là một cách thuận tiện và dễ dàng để thực hiện một cái gì đó không đồng bộ và song song.

Thông thường một Nhiệm vụ là tất cả những gì bạn cần, tôi không thể nhớ nếu tôi đã từng sử dụng một chủ đề cho một cái gì đó ngoài thử nghiệm.

Bạn có thể hoàn thành tương tự với một chuỗi (với rất nhiều nỗ lực) như bạn có thể với một nhiệm vụ.

Chủ đề

int result = 0;
Thread thread = new System.Threading.Thread(() => { 
    result = 1; 
});
thread.Start();
thread.Join();
Console.WriteLine(result); //is 1

Bài tập

int result = await Task.Run(() => {
    return 1; 
});
Console.WriteLine(result); //is 1

Một tác vụ theo mặc định sẽ sử dụng Threadpool, giúp tiết kiệm tài nguyên vì việc tạo các chủ đề có thể tốn kém. Bạn có thể thấy một Nhiệm vụ như một sự trừu tượng hóa ở cấp độ cao hơn trên các luồng.

Như bài viết này chỉ ra, tác vụ cung cấp các tính năng mạnh mẽ sau luồng.

  • Nhiệm vụ được điều chỉnh để tận dụng bộ xử lý đa lõi.

  • Nếu hệ thống có nhiều tác vụ thì nó sử dụng nhóm luồng CLR bên trong và do đó không có chi phí liên kết với việc tạo một luồng chuyên dụng bằng cách sử dụng Thread. Cũng giảm thời gian chuyển ngữ cảnh giữa nhiều chủ đề.

  • Tác vụ có thể trả về một kết quả. Không có cơ chế trực tiếp để trả về kết quả từ luồng.
  • Chờ trên một tập hợp các nhiệm vụ, mà không có một cấu trúc báo hiệu.

  • Chúng ta có thể xâu chuỗi các nhiệm vụ lại với nhau để thực hiện lần lượt từng nhiệm vụ.

  • Thiết lập mối quan hệ cha mẹ / con cái khi một nhiệm vụ được bắt đầu từ một nhiệm vụ khác.

  • Ngoại lệ nhiệm vụ con có thể truyền đến nhiệm vụ cha mẹ.

  • Nhiệm vụ hỗ trợ hủy bỏ thông qua việc sử dụng mã thông báo hủy.

  • Việc triển khai không đồng bộ rất dễ dàng trong nhiệm vụ, sử dụng các từ khóa 'async' và 'await'.

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.