Sự khác biệt giữa Task.Run () và Task.Factory.StartNew ()


189

Tôi có Phương pháp:

private static void Method()
{
    Console.WriteLine("Method() started");

    for (var i = 0; i < 20; i++)
    {
        Console.WriteLine("Method() Counter = " + i);
        Thread.Sleep(500);
    }

    Console.WriteLine("Method() finished");
}

Và tôi muốn bắt đầu phương pháp này trong một Nhiệm vụ mới. Tôi có thể bắt đầu nhiệm vụ mới như thế này

var task = Task.Factory.StartNew(new Action(Method));

hoặc cái này

var task = Task.Run(new Action(Method));

Nhưng có sự khác biệt giữa Task.Run()Task.Factory.StartNew(). Cả hai đều đang sử dụng ThreadPool và bắt đầu Phương thức () ngay sau khi tạo phiên bản của Tác vụ. Khi nào chúng ta nên sử dụng biến thể đầu tiên và khi thứ hai?


6
Trên thực tế, StartNew không phải sử dụng ThreadPool, hãy xem blog tôi liên kết đến trong câu trả lời của tôi. Vấn đề là StartNewmặc định sử dụng TaskScheduler.Currentcó thể là nhóm luồng nhưng cũng có thể là luồng UI.
Scott Chamberlain

Câu trả lời:


194

Phương thức thứ hai Task.Run, đã được giới thiệu trong phiên bản sau của .NET framework (trong .NET 4.5).

Tuy nhiên, phương pháp đầu tiên Task.Factory.StartNew, mang đến cho bạn cơ hội để xác định rất nhiều điều hữu ích về chủ đề bạn muốn tạo, trong khi Task.Runkhông cung cấp điều này.

Chẳng hạn, giả sử bạn muốn tạo một chuỗi tác vụ chạy dài. Nếu một luồng của nhóm luồng sẽ được sử dụng cho tác vụ này, thì đây có thể được coi là lạm dụng nhóm luồng.

Một điều bạn có thể làm để tránh điều này sẽ là chạy tác vụ trong một luồng riêng biệt. Một luồng mới được tạo sẽ dành riêng cho nhiệm vụ này và sẽ bị hủy khi nhiệm vụ của bạn đã hoàn thành. Bạn không thể đạt được điều này với Task.Run, trong khi bạn có thể làm như vậy với Task.Factory.StartNew, như dưới đây:

Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);

Như đã nêu ở đây :

Vì vậy, trong Bản xem trước dành cho nhà phát triển .NET Framework 4.5, chúng tôi đã giới thiệu phương thức Task.Run mới. Điều này không có gì làm ảnh hưởng đến Task.Factory.StartNew, nhưng đơn giản chỉ nên nghĩ là một cách nhanh chóng để sử dụng Task.Factory.StartNew mà không cần chỉ định một loạt các tham số. Đó là một phím tắt. Trong thực tế, Task.Run thực sự được triển khai theo cùng một logic được sử dụng cho Task.Factory.StartNew, chỉ truyền vào một số tham số mặc định. Khi bạn chuyển một hành động cho nhiệm vụ.Run:

Task.Run(someAction);

điều đó hoàn toàn tương đương với:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

4
Tôi có một đoạn mã mà statemente that’s exactly equivalent tokhông giữ.
Emaborsa

7
@Emaborsa Tôi sẽ đánh giá cao Nếu bạn có thể đăng đoạn mã này và giải thích lập luận của bạn. Cảm ơn trước !
Christos

4
@Emaborsa Bạn có thể tạo một ý chính, gist.github.com và chia sẻ nó. Tuy nhiên, ngoại trừ việc chia sẻ ý chính này, vui lòng xác định cách bạn đã đi đến kết quả mà cụm từ tha's exactly equivalent tokhông giữ được. Cảm ơn trước. Nó sẽ được tốt đẹp để giải thích với nhận xét về mã của bạn. Cảm ơn :)
Christos

8
Theo mặc định, điều đáng nói là task.Run unrap lồng nhau. Tôi khuyên bạn nên đọc bài viết này về sự khác biệt chính: blog.msdn.microsoft.com/pfxteam/2011/10/24/ mẹo
Pawel Maga

1
@ The0bserver không, nó là TaskScheduler.Default. Xin vui lòng xem tại đây Referenceource.microsoft.com/#mscorlib/system/threading/T task / .
Christos

44

Mọi người đã đề cập rằng

Task.Run(A);

Tương đương với

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

Nhưng không ai đề cập đến điều đó

Task.Factory.StartNew(A);

Tương đương với:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);

Như bạn có thể thấy hai tham số khác nhau cho Task.RunTask.Factory.StartNew:

  1. TaskCreationOptions- Task.Runsử dụng TaskCreationOptions.DenyChildAttachcó nghĩa là các nhiệm vụ trẻ em không thể được gắn liền với cha mẹ, hãy xem xét điều này:

    var parentTask = Task.Run(() =>
    {
        var childTask = new Task(() =>
        {
            Thread.Sleep(10000);
            Console.WriteLine("Child task finished.");
        }, TaskCreationOptions.AttachedToParent);
        childTask.Start();
    
        Console.WriteLine("Parent task finished.");
    });
    
    parentTask.Wait();
    Console.WriteLine("Main thread finished.");

    Khi chúng tôi gọi parentTask.Wait(), childTasksẽ không được chờ đợi, mặc dù chúng tôi đã chỉ định TaskCreationOptions.AttachedToParentcho nó, điều này là do TaskCreationOptions.DenyChildAttachcấm trẻ em gắn vào nó. Nếu bạn chạy cùng mã với Task.Factory.StartNewthay vì Task.Run, parentTask.Wait()sẽ chờ childTaskTask.Factory.StartNewsử dụngTaskCreationOptions.None

  2. TaskScheduler- Task.Runsử dụng TaskScheduler.Defaultcó nghĩa là bộ lập lịch tác vụ mặc định (công cụ chạy các tác vụ trên Thread Pool) sẽ luôn được sử dụng để chạy các tác vụ. Task.Factory.StartNewmặt khác sử dụng TaskScheduler.Currentcó nghĩa là bộ lập lịch của luồng hiện tại, nó có thể TaskScheduler.Defaultnhưng không phải luôn luôn. Trong thực tế khi phát triển Winformshoặc WPFứng dụng, cần phải cập nhật UI từ luồng hiện tại, để thực hiện điều này mọi người sử dụng TaskScheduler.FromCurrentSynchronizationContext()trình lập lịch tác vụ, nếu bạn vô tình tạo một tác vụ chạy dài khác bên trong tác vụ sử dụng TaskScheduler.FromCurrentSynchronizationContext()trình lập lịch biểu thì UI sẽ bị đóng băng. Một lời giải thích chi tiết hơn về điều này có thể được tìm thấy ở đây

Vì vậy, nói chung nếu bạn không sử dụng tác vụ con lồng nhau và luôn muốn các tác vụ của mình được thực thi trên Thread Pool thì tốt hơn nên sử dụng Task.Run, trừ khi bạn có một số tình huống phức tạp hơn.


1
Đây là một mẹo tuyệt vời, nên là câu trả lời được chấp nhận
Ali Bayat

30

Xem bài viết blog này mô tả sự khác biệt. Về cơ bản làm:

Task.Run(A)

Cũng giống như làm:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   

28

Đã Task.Rungiới thiệu trong phiên bản .NET framework mới hơn và nó được khuyến khích .

Bắt đầu với .NET Framework 4.5, phương thức Task.Run là cách được đề xuất để khởi chạy một tác vụ ràng buộc tính toán. Chỉ sử dụng phương pháp StartNew khi bạn yêu cầu điều khiển chi tiết cho một tác vụ dài hạn, tính toán.

Task.Factory.StartNewnhiều lựa chọn hơn, Task.Runlà một tốc ký:

Phương thức Run cung cấp một tập hợp các tình trạng quá tải giúp bạn dễ dàng bắt đầu một tác vụ bằng cách sử dụng các giá trị mặc định. Nó là một thay thế nhẹ cho quá tải StartNew.

Và bằng cách viết tắt tôi có nghĩa là một phím tắt kỹ thuật :

public static Task Run(Action action)
{
    return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
        TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}

21

Theo bài đăng này của Stephen Cleary, Task.Factory.StartNew () rất nguy hiểm:

Tôi thấy rất nhiều mã trên blog và trong các câu hỏi SO sử dụng Task.Factory.StartNew để quay vòng công việc trên một luồng nền. Stephen Toub có một bài viết blog tuyệt vời giải thích lý do tại sao Task.Run tốt hơn Task.Factory.StartNew, nhưng tôi nghĩ rằng nhiều người chỉ chưa đọc nó (hoặc không hiểu nó). Vì vậy, tôi đã đưa ra những lập luận tương tự, thêm một số ngôn ngữ mạnh mẽ hơn và chúng ta sẽ thấy điều này diễn ra như thế nào. :) StartNew cung cấp nhiều tùy chọn hơn so với Task.Run, nhưng nó khá nguy hiểm, như chúng ta sẽ thấy. Bạn nên chọn Task.Run hơn Task.Factory.StartNew trong mã async.

Dưới đây là những lý do thực tế:

  1. Không hiểu đại biểu async. Điều này thực sự giống như điểm 1 trong các lý do tại sao bạn muốn sử dụng StartNew. Vấn đề là khi bạn chuyển một ủy nhiệm không đồng bộ cho StartNew, việc cho rằng tác vụ được trả lại đại diện cho đại biểu đó là điều tự nhiên. Tuy nhiên, vì StartNew không hiểu các đại biểu không đồng bộ, nên nhiệm vụ đó thực sự đại diện chỉ là khởi đầu của đại biểu đó. Đây là một trong những cạm bẫy đầu tiên mà các lập trình viên gặp phải khi sử dụng StartNew trong mã async.
  2. Lịch trình mặc định khó hiểu. OK, lừa thời gian câu hỏi: trong đoạn mã dưới đây, phương thức nào mà Phương pháp A A chạy trên?
Task.Factory.StartNew(A);

private static void A() { }

Chà, bạn biết đó là một câu hỏi mẹo, hả? Nếu bạn đã trả lời một chủ đề pool pool chủ đề, tôi xin lỗi, nhưng điều đó không đúng. Một người khác sẽ chạy trên bất cứ thứ gì mà TaskScheduler hiện đang thực thi!

Vì vậy, điều đó có nghĩa là nó có khả năng có thể chạy trên luồng UI nếu một thao tác hoàn thành và nó trở lại luồng UI do tiếp tục như Stephen Cleary giải thích đầy đủ hơn trong bài đăng của mình.

Trong trường hợp của tôi, tôi đã cố chạy các tác vụ trong nền khi tải một tệp dữ liệu để xem trong khi cũng hiển thị một hình ảnh động bận rộn. Hoạt hình bận không hiển thị khi sử dụng Task.Factory.StartNew()nhưng hoạt ảnh hiển thị đúng khi tôi chuyển sang Task.Run().

Để biết chi tiết, vui lòng xem https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html


1

Ngoài những điểm tương đồng, ví dụ như Task.Run () là một cách viết tắt của Task.Factory.StartNew (), có một sự khác biệt nhỏ giữa hành vi của họ trong trường hợp đại biểu đồng bộ hóa và không đồng bộ.

Giả sử có hai phương pháp sau:

public async Task<int> GetIntAsync()
{
    return Task.FromResult(1);
}

public int GetInt()
{
    return 1;
}

Bây giờ hãy xem xét các mã sau đây.

var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());

Ở đây cả sync1 và sync2 đều thuộc loại Nhiệm vụ <int>

Tuy nhiên, sự khác biệt đến trong trường hợp phương pháp async.

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());

Trong trường hợp này, async1 thuộc loại Nhiệm vụ <int>, tuy nhiên async2 thuộc loại Nhiệm vụ <Nhiệm vụ <int >>


Yeap, vì Task.Runđã tích hợp sẵn chức năng mở khóa của Unwrapphương thức. Dưới đây là một bài viết trên blog giải thích lý do đằng sau quyết định này.
Theodor Zoulias

-8

Trong ứng dụng của tôi có gọi hai dịch vụ, tôi đã so sánh cả Task.Run và Task.Factory.StartNew . Tôi thấy rằng trong trường hợp của tôi cả hai đều hoạt động tốt. Tuy nhiên, cái thứ hai nhanh hơn.


Tôi không biết tại sao câu trả lời này xứng đáng với số phiếu giảm "10", mặc dù nó có thể không đúng hoặc hữu ích ...
Mayer Spitzer
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.