ทำไม Can CancelToken จึงแยกจาก Can CancelTokenSource


139

ฉันกำลังมองหาเหตุผลว่าทำไม. NET CancellationTokenstruct จึงถูกนำมาใช้นอกเหนือจากCancellationTokenSourceคลาส ฉันเข้าใจวิธีการใช้ API แต่ต้องการเข้าใจด้วยว่าเหตุใดจึงออกแบบมาเช่นนั้น

กล่าวคือทำไมเราถึงมี:

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

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

แทนที่จะส่งผ่านโดยตรงCancellationTokenSourceเช่น:

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

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

นี่เป็นการเพิ่มประสิทธิภาพตามข้อเท็จจริงที่ว่าการตรวจสอบสถานะการยกเลิกเกิดขึ้นบ่อยกว่าการส่งโทเค็นรอบ ๆ หรือไม่

เพื่อให้CancellationTokenSourceสามารถติดตามและอัปเดตCancellationTokensและสำหรับแต่ละโทเค็นการตรวจสอบการยกเลิกคือการเข้าถึงฟิลด์ภายในหรือไม่?

เนื่องจากบูลที่ระเหยได้โดยไม่มีการล็อคก็เพียงพอแล้วในทั้งสองกรณีฉันยังไม่เห็นว่าทำไมถึงเร็วขึ้น

ขอบคุณ!

คำตอบ:


113

ฉันมีส่วนร่วมในการออกแบบและการใช้งานชั้นเรียนเหล่านี้

คำตอบสั้น ๆ คือ " แยกความกังวล " เป็นเรื่องจริงที่มีกลยุทธ์การใช้งานที่หลากหลายและบางอย่างก็ง่ายกว่าอย่างน้อยก็เกี่ยวกับระบบประเภทและการเรียนรู้เบื้องต้น อย่างไรก็ตาม CTS และ CT มีไว้สำหรับใช้ในสถานการณ์ต่างๆที่ยอดเยี่ยม (เช่นสแต็กไลบรารีแบบลึกการคำนวณแบบขนาน async ฯลฯ ) ดังนั้นจึงได้รับการออกแบบโดยคำนึงถึงกรณีการใช้งานที่ซับซ้อนมากมาย เป็นการออกแบบเพื่อส่งเสริมรูปแบบที่ประสบความสำเร็จและกีดกันรูปแบบการต่อต้านโดยไม่ทำให้ประสิทธิภาพลดลง

หากประตูถูกเปิดทิ้งไว้เนื่องจาก API ทำงานผิดปกติประโยชน์ของการออกแบบการยกเลิกอาจถูกกัดเซาะอย่างรวดเร็ว

CancellationTokenSource == "ทริกเกอร์การยกเลิก" และสร้างผู้ฟังที่เชื่อมโยง

CancellationToken == "ฟังการยกเลิก"


7
ขอบคุณมาก! ความรู้ภายในขอชื่นชมจริงๆ
Andrey Tarantsov

stackoverflow.com/questions/39077497/… คุณมีความคิดว่าควรจะหมดเวลาเริ่มต้นสำหรับอินพุตสตรีมของ StreamSocket หรือไม่? เมื่อฉันใช้ Canceltoken เพื่อยกเลิกการอ่านมันจะปิดซ็อกเก็ตที่เกี่ยวข้องด้วย จะมีวิธีใดที่จะเอาชนะปัญหานี้ได้หรือไม่?
sam18

@ ไมค์ - แค่อยากรู้อยากเห็น: ทำไมคุณถึงไม่สามารถเรียกสิ่งต่างๆเช่น ThrowIfCancellationRequested () บน CTS เหมือนที่คุณสามารถทำได้ใน CT
rory.ap

พวกเขามีความรับผิดชอบที่แตกต่างกันและมันไม่ละเอียดเกินไปที่จะเขียน cts.Token.ThrowIfCancellationRequested () ดังนั้นเราจึงไม่ได้เพิ่ม Listener API โดยตรงบน CTS
Mike Liddell

@ ไมค์ทำไมการยกเลิกโทเค็นจึงเป็นโครงสร้างไม่ใช่คลาส? ฉันไม่เห็นข้อดีของ
อาวุธเลย

87

ฉันมีคำถามที่แน่นอนและฉันต้องการที่จะเข้าใจเหตุผลเบื้องหลังการออกแบบนี้

คำตอบที่ได้รับการยอมรับมีเหตุผลที่ถูกต้อง นี่คือคำยืนยันจากทีมงานที่ออกแบบฟีเจอร์นี้ (เน้นของฉัน):

รูปแบบใหม่สองประเภทเป็นพื้นฐานของกรอบงาน: A CancellationTokenเป็นโครงสร้างที่แสดงถึง 'คำขอยกเลิกที่เป็นไปได้' โครงสร้างนี้ถูกส่งไปยังการเรียกใช้เมธอดเป็นพารามิเตอร์และเมธอดสามารถสำรวจความคิดเห็นหรือลงทะเบียนการเรียกกลับที่จะเริ่มทำงานเมื่อมีการร้องขอการยกเลิก A CancellationTokenSourceเป็นคลาสที่จัดเตรียมกลไกสำหรับการเริ่มต้นการร้องขอการยกเลิกและมีToken คุณสมบัติสำหรับการรับโทเค็นที่เกี่ยวข้อง เป็นเรื่องปกติที่จะรวมคลาสทั้งสองนี้เข้าด้วยกันแต่การออกแบบนี้ช่วยให้การดำเนินการหลักสองอย่าง (การเริ่มต้นคำขอยกเลิกเทียบกับการสังเกตและการตอบสนองต่อการยกเลิก) สามารถแยกออกจากกัน โดยเฉพาะอย่างยิ่งวิธีการที่ใช้เพียงCancellationTokenสามารถสังเกตคำขอยกเลิก แต่ไม่สามารถเริ่มต้นได้

ลิงก์: .NET 4 กรอบการยกเลิก

ในความคิดของฉันความจริงที่CancellationTokenสามารถสังเกตเห็นสถานะและไม่เปลี่ยนแปลงมันเป็นสิ่งสำคัญอย่างยิ่ง คุณสามารถแจกโทเค็นเหมือนขนมและไม่ต้องกังวลว่าจะมีคนอื่นที่ไม่ใช่คุณยกเลิก ปกป้องคุณจากรหัสของบุคคลที่สามที่ไม่เป็นมิตร ใช่โอกาสที่จะผอม แต่ฉันชอบการรับประกันเป็นการส่วนตัว

ฉันยังรู้สึกว่ามันทำให้ API สะอาดขึ้นและหลีกเลี่ยงความผิดพลาดโดยไม่ได้ตั้งใจและส่งเสริมการออกแบบส่วนประกอบที่ดีขึ้น

ลองดู API สาธารณะสำหรับทั้งสองคลาสนี้

Can CancelToken API

Can CancelTokenSource API

ถ้าคุณจะรวมเข้าด้วยกันเมื่อเขียน LongRunningFunction ฉันจะเห็นวิธีการต่างๆเช่น 'ยกเลิก' ที่เกินพิกัดซึ่งฉันไม่ควรใช้ ส่วนตัวฉันเกลียดที่จะดูวิธีการ Dispose เช่นกัน

ฉันคิดว่าการออกแบบคลาสในปัจจุบันเป็นไปตามปรัชญา 'หลุมแห่งความสำเร็จ' ซึ่งจะแนะนำให้นักพัฒนาสร้างส่วนประกอบที่ดีขึ้นซึ่งสามารถจัดการกับTaskการยกเลิกแล้วนำมาประกอบเข้าด้วยกันเพื่อสร้างเวิร์กโฟลว์ที่ซับซ้อน

ให้ฉันถามคำถามคุณสงสัยหรือไม่ว่าจุดประสงค์ของโทเค็นคืออะไรลงทะเบียน? มันไม่สมเหตุสมผลสำหรับฉัน จากนั้นฉันอ่านการยกเลิกในชุดข้อความที่มีการจัดการและทุกอย่างก็กระจ่างใส

ฉันเชื่อว่าการออกแบบกรอบการยกเลิกใน TPL เป็นสิ่งที่ดีที่สุด


2
หากคุณสงสัยว่าCancellationTokenSourceจริง ๆ แล้วสามารถเริ่มต้นการขอยกเลิกที่มันเกี่ยวข้องโทเค็น (ไม่สามารถโทเค็นทำมันด้วยตัวเอง): ผู้ CancellationToken มีนี้คอนสตรัคภายใน internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }และคุณสมบัตินี้: public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }CancellationTokenSource ใช้คอนสตรัคภายในเพื่อโทเค็นมีการอ้างอิงถึง source (m_source)
chviLadislav

65

พวกเขาแยกจากกันไม่ใช่ด้วยเหตุผลทางเทคนิค แต่เป็นเหตุผลทางความหมาย หากคุณดูการใช้งาน CancellationTokenภายใต้ ILSpy คุณจะพบว่ามันเป็นเพียงเครื่องห่อหุ้มรอบ ๆCancellationTokenSource(และไม่มีประสิทธิภาพที่แตกต่างไปจากการอ้างอิง)

พวกเขาให้ฟังก์ชันการทำงานที่แยกออกจากกันเพื่อให้สามารถคาดเดาสิ่งต่างๆได้มากขึ้น: เมื่อคุณผ่านวิธีการ a CancellationTokenคุณจะรู้ว่าคุณยังคงเป็นคนเดียวที่สามารถยกเลิกได้ แน่นอนว่าวิธีนี้ยังสามารถโยน a TaskCancelledExceptionได้ แต่CancellationTokenตัวมันเอง - และวิธีการอื่น ๆ ที่อ้างถึงโทเค็นเดียวกันจะยังคงปลอดภัย


ขอบคุณ! ปฏิกิริยาการกะพริบของฉันคือตามความหมายมันเป็นวิธีที่น่าสงสัยพอ ๆ กับการให้ IReadOnlyList มันฟังดูเป็นไปได้มากดังนั้นการยอมรับคำตอบของคุณ
Andrey Tarantsov

1
เมื่อคิดต่อไปฉันพบว่าตัวเองกำลังตั้งคำถามถึงความเป็นไปได้ ถ้าอย่างนั้นมันจะสมเหตุสมผลกว่าหรือไม่ที่จะให้อินเทอร์เฟซ ICancellationToken ที่ Can CancelTokenSource จะนำไปใช้ ฉันขอให้ใครสักคนจากทีม. NET เข้ามา
Andrey Tarantsov

CancellationTokenSourceที่ยังคงอาจจะส่งผลให้มีการคัดเลือกนักแสดงฝีมือโปรแกรมเมอร์ คุณคิดว่าคุณสามารถพูดว่า "อย่าทำอย่างนั้น" แต่ผู้คน (รวมถึงฉันด้วย!) มักจะทำสิ่งเหล่านี้เป็นครั้งคราวเพื่อให้ได้ฟังก์ชันการทำงานที่ซ่อนอยู่บางอย่างและมันจะเกิดขึ้น นั่นคือทฤษฎีปัจจุบันของฉันอย่างน้อยที่สุด
Cory Nelson

1
นั่นไม่ใช่วิธีที่คุณออกแบบ API ในกรณีส่วนใหญ่เว้นแต่จะเกี่ยวข้องกับความปลอดภัย ฉันอยากจะเดิมพันด้วยคำอธิบายอื่น ๆ เช่น“ การรักษาตัวเลือกของพวกเขาไว้สำหรับการเพิ่มประสิทธิภาพในอนาคต” แต่ถึงกระนั้นของคุณก็ยังดีที่สุด
Andrey Tarantsov

สวัสดีฉันหวังว่าคุณจะแก้ตัวให้ฉันมอบหมายคำตอบที่ได้รับการยอมรับให้กับบุคคลที่เกี่ยวข้องกับการใช้งานอีกครั้ง ในขณะที่คุณทั้งสองพูดในสิ่งเดียวกันฉันคิดว่า SO ได้ประโยชน์จากคำตอบสุดท้ายที่อยู่ด้านบน
Andrey Tarantsov

10

CancellationTokenเป็น struct ดังนั้นหลายสำเนาสามารถอยู่เนื่องจากผ่านมันไปพร้อมกับวิธีการ

CancellationTokenSourceชุดรัฐสำเนาของโทเค็นทั้งหมดที่เมื่อโทรCancelกับแหล่งที่มา ดูหน้า MSDN นี้

เหตุผลในการออกแบบอาจเป็นเพียงเรื่องของการแยกความกังวลและความเร็วของโครงสร้าง


1
ขอบคุณ. โปรดทราบว่าคำตอบอื่นบอกว่าในทางเทคนิคแล้ว (ใน IL) Can การยกเลิก TokenSource ไม่ได้ตั้งค่าสถานะของโทเค็น โทเค็นจะห่อข้อมูลอ้างอิงจริงไปยัง Can CancelTokenSource แทนและเพียงแค่เข้าถึงเพื่อตรวจสอบการยกเลิก ถ้ามีอะไรความเร็วจะหายไปที่นี่เท่านั้น
Andrey Tarantsov

+1. ฉันไม่เข้าใจ. หากเป็นประเภทค่าดังนั้นแต่ละวิธีจึงมีค่าของตัวเอง (ค่าแยกต่างหาก) แล้วเมธอดจะรู้ได้อย่างไรว่ามีการเรียกการยกเลิก? ไม่มีการอ้างอิงถึง TokenSource วิธีการทั้งหมดที่สามารถเห็นคือประเภทค่าท้องถิ่นเช่น "5" คุณสามารถอธิบาย ?
Royi Namir

1
@RoyiNamir: Can CancelToken แต่ละรายการมีการอ้างอิงส่วนตัว "m_source" ประเภท
Can CancelTokenSource

2

สิ่งCancellationTokenSourceนี้คือ "สิ่ง" ที่เป็นประเด็นในการยกเลิกไม่ว่าจะด้วยเหตุผลใดก็ตาม มันต้องมีวิธีในการ "ส่ง" การยกเลิกนั้นไปยังสิ่งที่CancellationTokenออกให้ทั้งหมด นั่นเป็นวิธีที่ตัวอย่างเช่น ASP.NET สามารถยกเลิกการดำเนินการเมื่อคำขอถูกยกเลิก แต่ละคำขอมีCancellationTokenSourceที่ส่งต่อการยกเลิกไปยังโทเค็นทั้งหมดที่ออก

สิ่งนี้ยอดเยี่ยมสำหรับการทดสอบหน่วย BTW - สร้างแหล่งโทเค็นการยกเลิกของคุณเองรับโทเค็นโทรหาCancelแหล่งที่มาและส่งโทเค็นไปยังรหัสของคุณที่ต้องจัดการกับการยกเลิก


ขอบคุณ - แต่ทั้งความรับผิดชอบ (ซึ่งจะเกี่ยวข้องมาก) จะได้รับในชั้นเรียนเดียวกัน อธิบายไม่ได้จริงๆว่าทำไมถึงแยกกัน
Andrey Tarantsov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.