Khi nào nên sử dụng Task.Delay, khi nào nên sử dụng Thread.S ngủ?


385

Có quy tắc tốt nào khi sử dụng Task.Delay so với Thread.S ngủ không?

  • Cụ thể, có một giá trị tối thiểu để cung cấp cho một cái có hiệu lực / hiệu quả hơn cái kia không?
  • Cuối cùng, vì Task.Delay gây ra chuyển đổi ngữ cảnh trên máy trạng thái không đồng bộ / đang chờ, nên có sử dụng nó không?

2
10ms là rất nhiều chu kỳ trong thế giới máy tính ...
Brad Christie

Nó nên nhanh như thế nào? Bạn có vấn đề gì về hiệu suất?
LB

4
Tôi nghĩ rằng câu hỏi thích hợp hơn là trong bối cảnh nào bạn dự định sử dụng một trong hai? Không có thông tin đó thì phạm vi quá rộng. Bạn có ý nghĩa gì bởi hiệu quả / hiệu quả? Bạn đang đề cập đến độ chính xác, hiệu quả năng lượng, vv? Tôi rất tò mò muốn biết trong bối cảnh này vấn đề gì.
James World

4
Tối thiểu là 15.625 msec, các giá trị nhỏ hơn tốc độ ngắt của đồng hồ không có hiệu lực. Task.Delay luôn đốt cháy một System.Threading.Timer, Ngủ không có phí. Bạn không lo lắng về chi phí khi bạn viết mã mà không làm gì cả.
Hans Passant

một cái gì đó mà tôi không thấy được đề cập, nhưng tôi nghĩ là quan trọng, đó là Task.Delay hỗ trợ CancellingToken, nghĩa là bạn có thể làm gián đoạn sự chậm trễ, ví dụ, nếu bạn đang sử dụng nó để làm chậm quá trình xử lý. điều này cũng có nghĩa là quá trình của bạn có thể đáp ứng nhanh chóng khi bạn muốn hủy bỏ nó. nhưng bạn có thể đạt được điều tương tự với Thread.S ngủ làm cho khoảng thời gian chu kỳ ngủ ngắn hơn và kiểm tra mã thông báo Token.
Droa

Câu trả lời:


369

Sử dụng Thread.Sleepkhi bạn muốn chặn chuỗi hiện tại.

Sử dụng Task.Delaykhi bạn muốn độ trễ logic mà không chặn chuỗi hiện tại.

Hiệu quả không nên là mối quan tâm hàng đầu với các phương pháp này. Việc sử dụng trong thế giới thực chính của họ là bộ định thời thử lại cho các hoạt động I / O, theo thứ tự giây chứ không phải là mili giây.


3
Đó là trường hợp sử dụng chính tương tự: bộ đếm thời gian thử lại.
Stephen Cleary

4
Hoặc khi bạn không muốn nhai CPU trong một vòng lặp chính.
Eddie Parker

5
@RoyiNamir: Không. Không có "chủ đề khác". Trong nội bộ, nó được thực hiện với một bộ đếm thời gian.
Stephen Cleary

20
Đề nghị không lo lắng về hiệu quả là không sáng suốt. Thread.Sleepsẽ chặn luồng hiện tại gây ra chuyển đổi ngữ cảnh. Nếu bạn đang sử dụng nhóm luồng, điều này cũng có thể khiến luồng mới được phân bổ. Cả hai hoạt động đều khá nặng trong khi đa tác vụ hợp tác được cung cấp bởi Task.Delayvv được thiết kế để tránh tất cả các chi phí đó, tối đa hóa thông lượng, cho phép hủy bỏ và cung cấp mã sạch hơn.
Corillian

2
@LucaCremry onesi: I would use Thread.S ngủ` để chờ bên trong một phương thức đồng bộ. Tuy nhiên, tôi không bao giờ làm điều này trong mã sản xuất; theo kinh nghiệm của tôi, mọi thứ Thread.Sleeptôi từng thấy đều là dấu hiệu của một số vấn đề thiết kế cần được khắc phục.
Stephen Cleary

243

Sự khác biệt lớn nhất giữa Task.DelayThread.Sleepđó Task.Delaylà dự định chạy không đồng bộ. Nó không có ý nghĩa để sử dụng Task.Delaytrong mã đồng bộ. Đó là một ý tưởng RẤT xấu để sử dụng Thread.Sleeptrong mã không đồng bộ.

Thông thường bạn sẽ gọi Task.Delay() với awaittừ khóa:

await Task.Delay(5000);

hoặc, nếu bạn muốn chạy một số mã trước khi trì hoãn:

var sw = new Stopwatch();
sw.Start();
Task delay = Task.Delay(5000);
Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
await delay;

Đoán xem cái này sẽ in cái gì? Chạy trong 0,0070048 giây. Nếu chúng ta di chuyển await delayở trên Console.WriteLinethay vào đó, nó sẽ in Chạy trong 5,0020168 giây.

Hãy xem sự khác biệt với Thread.Sleep:

class Program
{
    static void Main(string[] args)
    {
        Task delay = asyncTask();
        syncCode();
        delay.Wait();
        Console.ReadLine();
    }

    static async Task asyncTask()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("async: Starting");
        Task delay = Task.Delay(5000);
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        await delay;
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("async: Done");
    }

    static void syncCode()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("sync: Starting");
        Thread.Sleep(5000);
        Console.WriteLine("sync: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("sync: Done");
    }
}

Hãy thử dự đoán những gì nó sẽ in ...

async: Bắt đầu
async: Chạy trong 0,0070048 giây
đồng bộ hóa: Bắt đầu
async: Chạy trong 5,0119008 giây
async:
Đồng bộ hóa xong : Chạy trong 5,0020168 giây
đồng bộ hóa: Xong

Ngoài ra, thật thú vị khi nhận thấy rằng Thread.Sleepchính xác hơn nhiều, độ chính xác ms không thực sự là một vấn đề, trong khi Task.Delaycó thể mất tối thiểu 15-30ms. Chi phí hoạt động trên cả hai chức năng là tối thiểu so với độ chính xác ms mà chúng có (sử dụng StopwatchClass nếu bạn cần thứ gì đó chính xác hơn). Thread.Sleepvẫn liên kết Chủ đề của bạn, Task.Delayphát hành nó để làm công việc khác trong khi bạn chờ đợi.


15
Tại sao lại là "một ý tưởng tồi RẤT khi sử dụng Thread.S ngủ trong mã không đồng bộ"?
trời

69
@sunside Một trong những ưu điểm chính của mã async là cho phép một luồng hoạt động trên nhiều tác vụ cùng một lúc, bằng cách tránh chặn các cuộc gọi. Điều này tránh sự cần thiết cho số lượng lớn các luồng riêng lẻ và cho phép một luồng xử lý nhiều yêu cầu cùng một lúc. Tuy nhiên, do mã async thường chạy trên luồng, không cần thiết phải chặn một luồng với việc Thread.Sleep()tiêu thụ toàn bộ một luồng có thể được sử dụng ở nơi khác. Nếu nhiều tác vụ được chạy với Thread.S ngủ (), có khả năng cao sẽ làm cạn kiệt tất cả các luồng xử lý luồng và cản trở nghiêm trọng hiệu năng.
Ryan

1
Hiểu rồi. Tôi đã thiếu khái niệm mã không đồng bộ theo nghĩa các asyncphương thức khi chúng được khuyến khích sử dụng. Về cơ bản, đó chỉ là một ý tưởng tồi để chạy Thread.Sleep()trong một luồng luồng, nói chung không phải là một ý tưởng tồi. Rốt cuộc, có TaskCreationOptions.LongRunningkhi đi theo con đường (mặc dù không được khuyến khích) Task.Factory.StartNew().
trời ngày

6
danh tiếng choawait wait
Eric Wu

2
@Reyhn Tài liệu về điều này là Tasl.Delaysử dụng bộ đếm thời gian hệ thống. Vì "Đồng hồ hệ thống" tích tắc "ở tốc độ không đổi.", Tốc độ đánh dấu của bộ hẹn giờ hệ thống là khoảng 16ms, mọi độ trễ bạn yêu cầu sẽ được làm tròn thành một số tích tắc của đồng hồ hệ thống, được bù theo thời gian cho đến lần đầu tiên đánh dấu Xem tài liệu Task.Delay msDN trên docs.microsoft.com/en-us/dotnet/api/ thép và cuộn xuống để nhận xét.
Dorus

28

nếu luồng hiện tại bị giết và bạn sử dụng Thread.Sleepvà nó đang thực thi thì bạn có thể nhận được a ThreadAbortException. Với Task.Delaybạn luôn có thể cung cấp mã thông báo hủy và duyên dáng giết nó. Đó là một lý do tôi sẽ chọn Task.Delay. xem http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-s ngủ-vs-task-delay.aspx

Tôi cũng đồng ý hiệu quả không phải là tối quan trọng trong trường hợp này.


2
Giả sử chúng ta có tình huống sau : await Task.Delay(5000). Khi tôi giết nhiệm vụ tôi nhận được TaskCanceledException(và triệt tiêu nó) nhưng chủ đề của tôi vẫn còn sống. Khéo léo! :)
AlexMelw

24

Tôi muốn thêm một cái gì đó. Trên thực tế, Task.Delaylà một cơ chế chờ dựa trên bộ đếm thời gian. Nếu bạn nhìn vào nguồn, bạn sẽ tìm thấy một tham chiếu đến một Timerlớp chịu trách nhiệm cho sự chậm trễ. Mặt khác, Thread.Sleepthực sự làm cho luồng hiện tại đi ngủ, theo cách đó bạn chỉ chặn và lãng phí một luồng. Trong mô hình lập trình async, bạn nên luôn luôn sử dụng Task.Delay()nếu bạn muốn điều gì đó (tiếp tục) xảy ra sau một số độ trễ.


'Đang chờ Task.Delay ()' giải phóng chuỗi để làm những việc khác cho đến khi hết giờ, rõ ràng 100%. Nhưng điều gì sẽ xảy ra nếu tôi không thể sử dụng 'await' vì phương thức không có tiền tố là 'async'? Sau đó, tôi chỉ có thể gọi 'Task.Delay ()'. Trong trường hợp đó , luồng vẫn bị chặn nhưng tôi có lợi thế là hủy Delay () . Đúng không?
Erik Stroeken

5
@ErikStroeken Bạn có thể chuyển mã thông báo hủy cho cả luồng và tác vụ. Task.Delay (). Wait () sẽ chặn, trong khi Task.Delay () chỉ tạo tác vụ nếu được sử dụng mà không phải chờ đợi. Những gì bạn làm với nhiệm vụ đó là tùy thuộc vào bạn, nhưng chủ đề vẫn tiếp tục.
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.