2012-04-23 27 views
50

Cố gắng hiểu sự khác biệt giữa TPL & không đồng bộ/đang chờ khi tạo chủ đề.Sự khác biệt giữa TPL & async/await (Xử lý chủ đề)

Tôi tin rằng TPL (TaskFactory.Startnew) hoạt động tương tự như ThreadPool.QueueUserWorkItem ở chỗ nó xếp hàng hoạt động trên một luồng trong nhóm luồng. Đó là tất nhiên trừ khi bạn sử dụng TaskCreationOptions.LongRunning tạo ra một chủ đề mới.

Tôi nghĩ async/chờ đợi sẽ làm việc tương tự như vậy về cơ bản:

TPL:

Factory.StartNew(() => DoSomeAsyncWork()) 
.ContinueWith( 
    (antecedent) => { 
     DoSomeWorkAfter(); 
    },TaskScheduler.FromCurrentSynchronizationContext()); 

Async/chờ:

await DoSomeAsyncWork(); 
DoSomeWorkAfter(); 

sẽ giống hệt nhau. Từ những gì tôi đã đọc nó có vẻ như async/await chỉ "đôi khi" tạo ra một chủ đề mới. Vì vậy, khi nào nó tạo ra một chủ đề mới và khi nào nó không tạo ra một chủ đề mới? Nếu bạn đã được giao dịch với cổng hoàn thành IO tôi có thể nhìn thấy nó không phải tạo một chủ đề mới nhưng nếu không tôi sẽ nghĩ rằng nó sẽ phải. Tôi đoán sự hiểu biết của tôi về FromCurrentSynchronizationContext luôn là một chút mờ. Tôi luôn luôn thông qua chủ đề giao diện người dùng.

+2

Thực ra, TaskCreationOptions.LongRunning không đảm bảo "chuỗi mới". Theo MSDN, * tùy chọn "LongRunning" chỉ cung cấp gợi ý cho trình lên lịch; nó không đảm bảo một chủ đề chuyên dụng. * Tôi thấy rằng trên con đường khó khăn. – eduncan911

+0

@ eduncan911 mặc dù những gì bạn nói về tài liệu là chính xác, tôi tra mã nguồn TPL trong khi trở lại và tôi khá chắc chắn rằng trên thực tế, một chuỗi chuyên dụng mới luôn được tạo khi 'TaskCreationOptions.LongRunning' được chỉ định. –

+0

@ZaidMasud: Bạn có thể muốn xem xét lại.Tôi biết nó đã được tổng hợp các chủ đề bởi vì 'Thread.CurrentThread.IsThreadPoolThread' đã trở lại đúng cho các chuỗi ngắn chạy của vài trăm mili giây. chưa kể đến các biến ThreadStatic mà tôi đã sử dụng chảy máu thành nhiều luồng, gây ra tất cả các loại havok. Tôi đã phải ép buộc mã của tôi để tạo mới nhiều Thread() s, cách học cũ, để đảm bảo một chủ đề chuyên dụng. Nói cách khác, tôi không thể sử dụng TaskFactory cho các chủ đề chuyên dụng. Tùy chọn, bạn có thể thực hiện 'TaskScheduler' của riêng bạn luôn trả về một chuỗi chuyên dụng. – eduncan911

Trả lời

64

tôi tin rằng TPL (TaskFactory.Startnew) hoạt động tương tự như ThreadPool.QueueUserWorkItem trong tôi rằng t xếp hàng hoạt động trên một chuỗi trong nhóm luồng.

Pretty much.

Từ những gì tôi đã đọc, có vẻ như không đồng bộ/chờ đợi "đôi khi" tạo chuỗi mới.

Thực ra, nó không bao giờ thực hiện. Nếu bạn muốn đa luồng, bạn phải tự mình thực hiện nó. Có một phương thức Task.Run mới chỉ là viết tắt của Task.Factory.StartNew và đây có lẽ là cách phổ biến nhất để bắt đầu một tác vụ trên nhóm luồng.

Nếu bạn đang xử lý cổng hoàn thành IO, tôi có thể thấy nó không phải tạo chuỗi mới nhưng nếu không tôi sẽ nghĩ nó sẽ phải làm.

Bingo. Vì vậy, các phương pháp như Stream.ReadAsync sẽ thực sự tạo một bao bọc Task xung quanh IOCP (nếu Stream có IOCP).

Bạn cũng có thể tạo một số tác vụ "không phải là I/O, không phải CPU". Một ví dụ đơn giản là Task.Delay, trả về một tác vụ hoàn thành sau một khoảng thời gian.

Điều thú vị về async/await là bạn có thể xếp hàng một số công việc để các hồ bơi thread (ví dụ, Task.Run), làm một số I/O-bound hoạt động (ví dụ: Stream.ReadAsync), và thực hiện một số hoạt động khác (ví dụ, Task.Delay) ... và tất cả đều là nhiệm vụ! Chúng có thể được chờ đợi hoặc sử dụng trong các kết hợp như Task.WhenAll.

Bất kỳ phương thức nào trả về Task đều có thể là await - không phải là phương thức async. Các thao tác Task.Delay và I/O-bound chỉ sử dụng TaskCompletionSource để tạo và hoàn thành tác vụ - điều duy nhất được thực hiện trên nhóm luồng là hoàn thành nhiệm vụ thực tế khi sự kiện xảy ra (hết giờ, hoàn thành I/O, v.v.).

Tôi đoán rằng sự hiểu biết của tôi về FromCurrentSynchronizationContext luôn có chút mờ. Tôi luôn luôn thông qua chủ đề giao diện người dùng.

Tôi đã viết an article trên SynchronizationContext. Phần lớn thời gian, SynchronizationContext.Current:

  • là ngữ cảnh giao diện người dùng nếu chuỗi hiện tại là một chuỗi giao diện người dùng.
  • là ngữ cảnh yêu cầu ASP.NET nếu chuỗi hiện tại đang phục vụ yêu cầu ASP.NET.
  • là ngữ cảnh của nhóm chủ đề khác.

Bất kỳ chủ đề nào có thể đặt SynchronizationContext riêng của mình, vì vậy có những ngoại lệ đối với các quy tắc trên.

Lưu ý rằng mặc định Task awaiter sẽ sắp xếp thời gian còn lại của async phương pháp trên hiện hành SynchronizationContextnếu nó không phải là null; nếu không nó sẽ đi vào hiện tại TaskScheduler. Hôm nay không quan trọng như vậy, nhưng trong tương lai gần nó sẽ là một sự phân biệt quan trọng.

Tôi đã tự viết async/await intro trên blog của mình và Stephen Toub gần đây đã đăng một số xuất sắc async/await FAQ.

Về "đồng thời" và "đa luồng", hãy xem this related SO question. Tôi có thể nói async cho phép đồng thời, có thể hoặc không thể đa luồng. Thật dễ dàng để sử dụng await Task.WhenAll hoặc await Task.WhenAny để xử lý đồng thời và trừ khi bạn sử dụng hồ bơi chủ đề một cách rõ ràng (ví dụ: Task.Run hoặc ConfigureAwait(false)), thì bạn có thể thực hiện nhiều thao tác đồng thời cùng một lúc (ví dụ: nhiều I/O hoặc khác các loại như Delay) - và không có chuỗi nào cần thiết cho chúng. Tôi sử dụng thuật ngữ "đồng thời một luồng" cho loại kịch bản này, mặc dù trong máy chủ ASP.NET, bạn thực sự có thể kết thúc với "zero đồng thời đã đọc". Đó là khá ngọt ngào.

+1

Câu trả lời hay. Tôi cũng khuyên bạn nên http://www.infoq.com/articles/Async-API-Design và bản trình bày tuyệt vời này: http://channel9.msdn.com/Events/TechEd/Europe/2013/DEV-B318. – Philippe

+0

Liên kết đầu tiên đã chết. –

+0

@FelipeDeveza Đã sửa, cảm ơn! –

8

async/chờ đợi cơ bản đơn giản hoá ContinueWith phương pháp (continuations trong Continuation Passing Style)

Nó không giới thiệu đồng thời - bạn vẫn phải làm điều đó cho mình (hoặc sử dụng phiên bản async của một phương pháp khuôn khổ.)

Vì vậy, phiên bản C# 5 sẽ là:

await Task.Run(() => DoSomeAsyncWork()); 
DoSomeWorkAfter(); 
+0

sao nó chạy DoSomeAsyncWork (phiên bản không đồng bộ/chờ) trong ví dụ trên? Nếu nó đang chạy trên chuỗi giao diện người dùng thì nó sẽ không chặn như thế nào? – coding4fun

+1

Ví dụ chờ đợi của bạn sẽ không biên dịch nếu 'DoSomeWorkAsync()' trả về khoảng trống hoặc một thứ không thể chờ đợi được. Từ ví dụ đầu tiên của bạn, tôi giả định rằng nó là một phương thức tuần tự mà bạn muốn chạy trên một luồng khác. Nếu bạn thay đổi nó để trả về một 'Task', mà không đưa ra sự đồng thời, thì có nó sẽ chặn. Theo nghĩa là nó sẽ thực hiện tuần tự và giống như mã bình thường trên chuỗi giao diện người dùng. 'await' chỉ sinh lợi nếu phương thức trả về một awaitable chưa hoàn thành. –

+0

tốt, tôi sẽ không nói nó chạy bất cứ nơi nào nó chọn để chạy. Bạn đã sử dụng Task.Run để thực thi mã trong DoSomeAsyncWork, vì vậy trong trường hợp này công việc của bạn sẽ được thực hiện trên một luồng threadpool. –

Các vấn đề liên quan