112

Tôi đang tìm kiếm một lý do tại sao .NET CancellingToken struct được giới thiệu cùng với lớp CancellingTokenSource. Tôi hiểu API được sử dụng như thế nào , nhưng cũng muốn hiểu tại sao nó được thiết kế theo cách đó.

Tức là tại sao chúng ta có:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts.Token);

...
public void SomeCancellableOperation(CancellationToken token) {
    ...
    token.ThrowIfCancellationRequested();
    ...
}

thay vì trực tiếp chuyển CancellingTokenSource xung quanh như:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts);

...
public void SomeCancellableOperation(CancellationTokenSource cts) {
    ...
    cts.ThrowIfCancellationRequested();
    ...
}

Đây có phải là một tối ưu hóa hiệu suất dựa trên thực tế là việc kiểm tra trạng thái hủy xảy ra thường xuyên hơn so với việc chuyển mã thông báo xung quanh?

Vì vậy, CancellingTokenSource có thể theo dõi và cập nhật CancellingTokens và đối với mỗi mã thông báo, kiểm tra hủy là truy cập trường địa phương?

Cho rằng một bool dễ bay hơi không có khóa là đủ trong cả hai trường hợp, tôi vẫn không thể hiểu tại sao điều đó sẽ nhanh hơn.

Cảm ơn!

|
81

Tôi đã tham gia vào việc thiết kế và thực hiện các lớp này.

Câu trả lời ngắn gọn là "tách mối quan tâm". Hoàn toàn đúng là có nhiều chiến lược thực hiện khác nhau và một số đơn giản hơn ít nhất là về hệ thống loại và học tập ban đầu. Tuy nhiên, CTS và CT được thiết kế để sử dụng trong rất nhiều tình huống (như ngăn xếp thư viện sâu, tính toán song song, không đồng bộ, v.v.) và do đó được thiết kế với nhiều trường hợp sử dụng phức tạp. Đây là một thiết kế nhằm khuyến khích các mẫu thành công và không khuyến khích các mẫu chống mà không làm giảm hiệu suất.

Nếu cánh cửa bị bỏ ngỏ cho các API hoạt động sai, thì tính hữu ích của thiết kế hủy có thể nhanh chóng bị xói mòn.

|
75

Tôi đã có cùng một câu hỏi và tôi muốn hiểu lý do căn bản đằng sau thiết kế này.

Câu trả lời được chấp nhận có lý do chính xác. Đây là xác nhận từ nhóm đã thiết kế tính năng này (nhấn mạnh của tôi):

Hai loại mới tạo thành nền tảng của khung: CancellingToken là một cấu trúc đại diện cho một 'yêu cầu hủy bỏ tiềm năng'. Cấu trúc này được truyền vào các cuộc gọi phương thức như là một tham số và phương thức có thể thăm dò trên nó hoặc đăng ký một cuộc gọi lại sẽ bị hủy khi yêu cầu hủy bỏ. CancellingTokenSource là một lớp cung cấp cơ chế để bắt đầu yêu cầu hủy và nó có thuộc tính Token để lấy mã thông báo liên quan. Sẽ là điều tự nhiên khi kết hợp hai lớp này thành một, nhưng thiết kế này cho phép hai thao tác chính (bắt đầu yêu cầu hủy so với quan sát và phản hồi hủy) được tách biệt rõ ràng. Cụ thể, các phương thức chỉ mất CancellingToken có thể quan sát yêu cầu hủy nhưng không thể bắt đầu yêu cầu hủy.

Liên kết: Khung hủy bỏ .NET 4

Theo tôi, việc CancellingToken chỉ có thể quan sát trạng thái và không thay đổi nó, là cực kỳ quan trọng. Bạn có thể phát mã thông báo như một viên kẹo và không bao giờ lo lắng rằng ai đó, ngoài bạn, sẽ hủy nó. Nó bảo vệ bạn khỏi mã bên thứ ba thù địch. Vâng, cơ hội là rất nhỏ, nhưng cá nhân tôi thích sự đảm bảo đó.

Tôi cũng cảm thấy rằng nó làm cho API sạch hơn và tránh được lỗi vô ý và thúc đẩy thiết kế thành phần tốt hơn.

Hãy xem API công khai cho cả hai lớp này.

Nếu bạn kết hợp chúng, khi viết LongRastyFunction, tôi sẽ thấy các phương thức giống như nhiều lần quá tải 'Hủy' mà tôi không nên sử dụng. Cá nhân, tôi ghét phải xem phương pháp Vứt bỏ là tốt.

Tôi nghĩ rằng thiết kế lớp hiện tại tuân theo triết lý 'hố thành công', nó hướng dẫn các nhà phát triển tạo ra các thành phần tốt hơn có thể xử lý hủy bỏ Nhiệm vụ và sau đó kết hợp chúng theo nhiều cách để tạo ra các quy trình công việc phức tạp.

Hãy để tôi hỏi bạn một câu hỏi, bạn có tự hỏi mục đích của token.Register là gì không? Nó không có ý nghĩa với tôi. Và sau đó tôi đọc Hủy trong Chủ đề được quản lý và mọi thứ trở nên rõ ràng.

Tôi tin rằng Thiết kế khung hủy trong TPL là đỉnh cao.

|
  • 1

    Nếu bạn tự hỏi làm thế nào CancellationTokenSourcethực sự có thể bắt đầu yêu cầu hủy trên mã thông báo được liên kết của nó (mã thông báo không thể tự thực hiện): CancellingToken có hàm tạo bên trong này: internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }và thuộc tính này: public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }CancellingTokenSource sử dụng hàm tạo bên trong, vì vậy mã thông báo có tham chiếu đến nguồn (m_source)

    – Hoàng Mộng Quỳnh 09:55:39 28/03/2017
63

Chúng riêng biệt không phải vì lý do kỹ thuật mà là ngữ nghĩa. Nếu bạn nhìn vào việc triển khai CancellationTokentheo ILSpy, bạn sẽ thấy đó chỉ là một trình bao bọc xung quanh CancellationTokenSource(và do đó không khác biệt về hiệu năng so với việc chuyển qua một tham chiếu).

Chúng cung cấp sự phân tách chức năng này để làm cho mọi thứ dễ dự đoán hơn: khi bạn truyền một phương thức a CancellationToken, bạn biết bạn vẫn là người duy nhất có thể hủy bỏ nó. Chắc chắn, phương thức vẫn có thể ném một TaskCancelledException, nhưng CancellationTokenchính nó - và bất kỳ phương thức nào khác tham chiếu cùng một mã thông báo - sẽ vẫn an toàn.

|
  • 1

    Cảm ơn! Phản ứng chớp mắt của tôi là, về mặt ngữ nghĩa, đó là một cách tiếp cận đáng ngờ, ngang bằng với việc cung cấp IReadOnlyList. Mặc dù nghe có vẻ rất hợp lý, vì vậy, chấp nhận câu trả lời của bạn.

    – Đặng Hồng Khanh 14:15:46 08/01/2013
  • 1

    Khi suy nghĩ thêm, tôi thấy mình nghi ngờ tính hợp lý. Sau đó, sẽ không có ý nghĩa hơn khi cung cấp giao diện ICancnameToken mà CancnameTokenSource sẽ triển khai? Tôi ước ai đó từ nhóm .NET sẽ hòa nhập.

    – Hồ Yến Trâm 14:20:16 08/01/2013
  • 1

    Điều đó vẫn có thể dẫn đến một lập trình viên xảo quyệt CancellationTokenSource. Bạn sẽ nghĩ rằng bạn chỉ có thể nói "đừng làm vậy", nhưng mọi người (bao gồm cả tôi!) Thỉnh thoảng vẫn làm những việc này để có được một số chức năng ẩn và điều đó sẽ xảy ra. Đó là lý thuyết hiện tại của tôi, ít nhất.

    – Nelly Quỳnh 14:45:20 08/01/2013
  • 1

    Đó không phải là cách bạn thiết kế API trong hầu hết các trường hợp, trừ khi nó liên quan đến bảo mật. Tôi thà đặt cược vào một số lời giải thích khác, như là giữ cho các tùy chọn của họ mở để tối ưu hóa hiệu suất trong tương lai. Tuy nhiên, của bạn là tốt nhất cho đến nay.

    – Tạ Hải Ðường 12:32:08 09/01/2013
  • 1

    Này, tôi hy vọng bạn sẽ tha thứ cho tôi khi chỉ định lại câu trả lời được chấp nhận cho người liên quan đến việc thực hiện. Trong khi cả hai bạn đều nói điều tương tự, tôi nghĩ SO được lợi từ câu trả lời dứt khoát đang đứng đầu.

    – Tạ Hồng Nhung 06:12:45 23/03/2015
9

CancellingToken là một cấu trúc để nhiều bản sao có thể tồn tại do truyền nó cho các phương thức.

CancellingTokenSource đặt trạng thái TẤT CẢ các bản sao của mã thông báo khi gọi Hủy trên nguồn. Xem trang MSDN này

Lý do cho thiết kế có thể chỉ là vấn đề tách biệt mối quan tâm và tốc độ của một cấu trúc.

|
  • 1

    Cảm ơn. Lưu ý rằng một câu trả lời khác nói rằng về mặt kỹ thuật (trong IL), CancellingTokenSource không đặt trạng thái mã thông báo; thay vào đó, các mã thông báo bao bọc một tham chiếu thực tế đến CancellingTokenSource và chỉ cần truy cập nó để kiểm tra hủy bỏ. Nếu bất cứ điều gì, tốc độ chỉ có thể bị mất ở đây.

    – Đặng Hồng Khanh 14:24:10 08/01/2013
  • 1

    +1. Tôi không hiểu nếu nó là một loại giá trị để mỗi phương thức có giá trị riêng (giá trị riêng). vậy làm thế nào để một phương thức biết nếu hủy bỏ đã được gọi? nó KHÔNG có bất kỳ tham chiếu nào đến TokenSource. tất cả các phương thức có thể thấy là một loại giá trị cục bộ như "5". bạn có thể giải thích ?

    – Hồ Yến Trâm 13:56:39 22/03/2013
  • 1

    @RoyiNamir: Mỗi CancellingToken có tham chiếu riêng "m_source" thuộc loại CancellingTokenSource

    – Nelly Quỳnh 17:51:35 07/02/2014
1

CancellingTokenSource là 'điều' đưa ra sự hủy bỏ, vì bất kỳ lý do gì. Nó cần một cách để 'gửi' sự hủy bỏ đó đến tất cả các Hủy bỏ mà nó đã ban hành. Đó là cách, ví dụ, ASP.NET có thể hủy các hoạt động khi yêu cầu bị hủy bỏ. Mỗi yêu cầu có một CTSource chuyển tiếp hủy bỏ tất cả các mã thông báo mà nó đã phát hành.

Điều này tuyệt vời để kiểm tra đơn vị BTW - tạo nguồn mã thông báo hủy của riêng bạn, nhận mã thông báo, gọi Hủy trên nguồn và chuyển mã thông báo đến mã của bạn để xử lý việc hủy.

|
  • 1

    Cảm ơn - nhưng cả hai nhiệm vụ (trong đó đang rất liên quan) có thể được trao cho cùng lớp. Không thực sự giải thích tại sao chúng là riêng biệt.

    – Đặng Hồng Khanh 14:22:15 08/01/2013
0

"Lý thuyết" của tôi là vai trò của CancellingToken là cung cấp trình bao bọc an toàn cho luồng để tránh sự tranh chấp .

Nếu chỉ có một lớp, do đó, một thể hiện duy nhất sẽ được sử dụng với bản sao của tham chiếu và bạn có rất nhiều luồng xử lý đăng ký để hủy bỏ, ví dụ, lớp này nên quản lý đồng thời bằng cách sử dụng một số khóa, làm giảm hiệu suất.

Trong khi đó với cấu trúc CancellingToken, bạn có một bản sao của mỗi cuộc gọi lại hàng đợi cho mỗi luồng để không có sự tranh chấp.

Đây là suy đoán thuần túy và tôi có thể hoàn toàn sai và / hoặc có thể có những lý do tốt khác cho thiết kế này.

Điều duy nhất chắc chắn là có ít nhất một lý do chính đáng, và thật xấu hổ khi loại quyết định này không được ghi nhận nhiều hơn vì nó là một cái nhìn sâu sắc vô giá khi sử dụng công nghệ.

|
  • 1

    Điều này là hoàn toàn sai. Như Cory nêu trong câu trả lời của mình, các phương thức về CancellationTokencơ bản chỉ gọi các phương thức trên CancellationTokenSource, không có hàng đợi các cuộc gọi lại cho mỗi phương thức CancellationToken. Và tranh luận của bạn về sự tranh chấp dù sao cũng không có nhiều ý nghĩa.

    – Tạ Hoàng Thư 19:05:42 22/03/2013
  • 1

    @svick Ouch. Đó là rất .. trực tiếp.

    – Hoàng Phước Thiện 11:49:04 15/01/2014

Câu trả lời của bạn (> 20 ký tự)

Bằng cách click "Đăng trả lời", bạn đồng ý với Điều khoản dịch vụ, Chính sách bảo mật and Chính sách cookie của chúng tôi.

Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ hoặc hỏi câu hỏi của bạn.