วัตถุที่ถูกล็อกจะยังคงถูกล็อกอยู่หรือไม่หากเกิดข้อยกเว้นขึ้นภายใน


87

ในแอพ ac # threading ถ้าฉันจะล็อคอ็อบเจกต์ให้เราพูดคิวและถ้ามีข้อยกเว้นเกิดขึ้นอ็อบเจกต์จะยังคงล็อคอยู่หรือไม่ นี่คือรหัสหลอก:

int ii;
lock(MyQueue)
{
   MyClass LclClass = (MyClass)MyQueue.Dequeue();
   try
   {
      ii = int.parse(LclClass.SomeString);
   }
   catch
   {
     MessageBox.Show("Error parsing string");
   }
}

ตามที่ฉันเข้าใจรหัสหลังจากการจับไม่ได้ดำเนินการ - แต่ฉันสงสัยว่าการล็อกจะถูกปลดปล่อยหรือไม่


1
ตามความคิดสุดท้าย (ดูการอัปเดต) - คุณควรจะล็อคไว้ในช่วงระยะเวลาของการจัดคิวเท่านั้น ... ทำการประมวลผลนอกล็อก
Marc Gravell

1
รหัสหลังจากจับจะดำเนินการเนื่องจากมีการจัดการข้อยกเว้น
cjk

ขอบคุณฉันคงพลาดคำถามนั้นไปฉันควรลบคำถามนี้หรือไม่
Vort3x

4
ดูเหมือนว่าโค้ดตัวอย่างจะไม่ดีสำหรับคำถามนี้ แต่คำถามก็ใช้ได้ทีเดียว
SalvadorGomez

โดย C # Designer - Lock & Exception
Jivan

คำตอบ:


91

อันดับแรก; คุณได้พิจารณา TryParse หรือไม่

in li;
if(int.TryParse(LclClass.SomeString, out li)) {
    // li is now assigned
} else {
    // input string is dodgy
}

การล็อกจะถูกคลายออกด้วยเหตุผล 2 ประการคือ lockประการแรกเป็นหลัก:

Monitor.Enter(lockObj);
try {
  // ...
} finally {
    Monitor.Exit(lockObj);
}

ประการที่สอง; คุณจับและไม่โยนข้อยกเว้นภายในซ้ำดังนั้นจึงlockไม่เห็นข้อยกเว้นจริง แน่นอนว่าคุณกำลังล็อคอยู่ตลอดระยะเวลาของกล่องข้อความซึ่งอาจเป็นปัญหาได้

ดังนั้นมันจะถูกปล่อยออกมาทั้งหมดยกเว้นข้อยกเว้นที่ไม่สามารถกู้คืนได้อย่างหายนะร้ายแรงที่สุด


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

13
วิธีการโยนข้อยกเว้นใหม่ ("เพื่อวัตถุประสงค์ในการอธิบาย"); ;-p
Marc Gravell

1
ยกเว้นว่าTheadAbortExceptionเกิดขึ้นระหว่างMonitor.Enterและtry: blogs.msdn.com/ericlippert/archive/2009/03/06/…
Igal Tabachnik

2
"ภัยพิบัติร้ายแรงที่ไม่สามารถกู้คืนได้" เช่นการข้ามลำธาร
Phil Cooper

99

ฉันทราบว่าไม่มีใครพูดถึงในคำตอบของพวกเขาสำหรับคำถามเก่า ๆ นี้ว่าการปลดล็อกข้อยกเว้นเป็นสิ่งที่อันตรายอย่างยิ่งที่ต้องทำ ใช่คำสั่งล็อคใน C # มีความหมาย "ในที่สุด"; เมื่อตัวควบคุมออกจากล็อคตามปกติหรือผิดปกติล็อคจะถูกคลายออก คุณทุกคนพูดถึงเรื่องนี้เหมือนเป็นเรื่องดี แต่มันเป็นสิ่งที่ไม่ดี! สิ่งที่ควรทำหากคุณมีพื้นที่ที่ถูกล็อกซึ่งทำให้เกิดข้อยกเว้นที่ไม่สามารถจัดการได้คือยุติกระบวนการที่เป็นโรคทันทีก่อนที่จะทำลายข้อมูลผู้ใช้มากขึ้นไม่ใช่ปล่อยล็อกและดำเนินการต่อ

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

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

หากคุณล็อคภูมิภาคของรหัสเพื่อดำเนินการกลายพันธุ์ที่ไม่มีหัวข้ออื่นอ่านรัฐในเวลาเดียวกันและการกลายพันธุ์พ่นแล้วถ้าข้อมูลไม่ได้เสียหายก่อนก็แน่ใจว่าอยู่ในขณะนี้ ซึ่งเป็นสิ่งสถานการณ์ที่ล็อคควรจะป้องกัน ตอนนี้รหัสที่รออ่านสถานะนั้นจะได้รับสิทธิ์เข้าถึงสถานะเสียหายทันทีและอาจเกิดปัญหาขึ้นเอง อีกครั้งสิ่งที่ถูกต้องคือยุติกระบวนการ

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


19
ปัญหานั้นค่อนข้างตรงกับการล็อก IMO หากคุณได้รับข้อยกเว้นที่คาดไว้คุณต้องการล้างข้อมูลทุกอย่างรวมถึงล็อค และหากคุณได้รับข้อยกเว้นที่ไม่คาดคิดแสดงว่าคุณมีปัญหาจะล็อกหรือไม่ก็ได้
CodesInChaos

10
ฉันคิดว่าสถานการณ์ที่อธิบายไว้ข้างต้นเป็นเรื่องทั่วไป บางครั้งมีข้อยกเว้นอธิบายเหตุการณ์ภัยพิบัติ บางครั้งก็ไม่ทำ แต่ละคนใช้รหัสต่างกัน เป็นสิ่งที่ถูกต้องอย่างสมบูรณ์ว่าข้อยกเว้นเป็นสัญญาณของเหตุการณ์พิเศษ แต่ไม่ใช่ภัยพิบัติ - สมมติว่าข้อยกเว้น = ภัยพิบัติกระบวนการยุติกรณีเฉพาะเจาะจงเกินไป ความจริงที่อาจเป็นเหตุการณ์หายนะไม่ได้ลบออกไปจากความถูกต้องของคำถาม - ความคิดแบบเดียวกันอาจทำให้คุณไม่จัดการกับข้อยกเว้นใด ๆ ซึ่งในกรณีนี้กระบวนการจะออก ...
Gerasimos R

1
@GerasimosR: อันที่จริง สองจุดที่จะเน้น ประการแรกควรถือว่าข้อยกเว้นเป็นภัยพิบัติจนกว่าจะพิจารณาได้ว่าไม่เป็นอันตราย ประการที่สองหากคุณได้รับข้อยกเว้นที่ไม่เป็นพิษเป็นภัยที่ถูกโยนออกจากพื้นที่ที่ถูกล็อกพื้นที่ที่ถูกล็อกอาจได้รับการออกแบบมาไม่ดี มันอาจจะทำงานมากเกินไปในตัวล็อค
Eric Lippert

1
ด้วยความเคารพ แต่ดูเหมือนว่าคุณกำลังพูดกับ Mr.Lippert ว่าการออกแบบ C # ของบล็อกล็อคซึ่งอยู่ใต้ฝากระโปรงนั้นใช้คำสั่งสุดท้ายเป็นสิ่งที่ไม่ดี แต่เราควรหยุดทุกแอพที่เคยมีข้อยกเว้นในคำสั่งล็อคที่ไม่ได้ติดอยู่ในล็อค บางทีนั่นอาจจะไม่ใช่สิ่งที่คุณจะพูด แต่ถ้าเป็นเช่นนั้นผมจริงๆมีความสุขที่ทีมไปเส้นทางที่พวกเขาทำและไม่ได้ของคุณ (หัวรุนแรงสำหรับเหตุผลที่ครูสอน!) เส้นทาง ด้วยความเคารพ
Nicholas Petersen

2
ในกรณีที่ไม่ชัดเจนว่าฉันหมายถึงอะไรโดยไม่สามารถประกอบได้: สมมติว่าเรามีวิธีการ "โอน" ที่ใช้สองรายการ s และ d ล็อค s ล็อค d ลบรายการจาก s เพิ่มรายการเป็น d ปลดล็อก d, ปลดล็อค s วิธีการที่ถูกต้องเท่านั้นถ้าไม่มีใครเคยพยายามที่จะถ่ายโอนจากรายการ X เพื่อรายการ Y ในเวลาเดียวกับคนอื่นพยายามที่จะถ่ายโอนจาก Y เพื่อ X ความถูกต้องของวิธีการถ่ายโอนไม่อนุญาตให้คุณสร้างวิธีแก้ปัญหาที่ถูกต้องสำหรับปัญหาที่ใหญ่กว่าจากนั้นเนื่องจากการล็อกเป็นการกลายพันธุ์ที่ไม่ปลอดภัยของสภาวะโลก ในการ "ถ่ายโอน" อย่างปลอดภัยคุณต้องรู้เกี่ยวกับการล็อกทุกโปรแกรม
Eric Lippert

43

ใช่ว่าจะปล่อยอย่างถูกต้อง lockทำหน้าที่เป็นtry/ finallyด้วยMonitor.Exit(myLock)ในที่สุดดังนั้นไม่ว่าคุณจะออกด้วยวิธีใดก็ตามจะถูกปล่อยออกมา ในฐานะที่เป็นด้านโน้ตcatch(... e) {throw e;}จะหลีกเลี่ยงที่ดีที่สุดในขณะที่ความเสียหายที่กองร่องรอยบนe; จะเป็นการดีกว่าที่จะไม่จับมันเลยหรืออีกทางเลือกหนึ่งคือใช้throw;แทนที่จะthrow e;โยนซ้ำ

หากคุณต้องการทราบจริงๆการล็อคใน C # 4 / .NET 4 คือ:

{
    bool haveLock = false;
    try {
       Monitor.Enter(myLock, ref haveLock);
    } finally {
       if(haveLock) Monitor.Exit(myLock);
    }
} 

14

"คำสั่งล็อกถูกรวบรวมเพื่อเรียก Monitor.Enter จากนั้นลอง ... บล็อกในที่สุดในบล็อกสุดท้าย Monitor.Exit จะถูกเรียก

การสร้างโค้ด JIT สำหรับทั้ง x86 และ x64 ทำให้แน่ใจได้ว่าการยกเลิกเธรดจะไม่เกิดขึ้นระหว่างมอนิเตอร์ป้อนการโทรและบล็อกการลองที่ตามมาทันที "

นำมาจาก: ไซต์นี้


1
มีอย่างน้อยหนึ่งกรณีที่ไม่เป็นความจริง: เธรดการแท้งในโหมดดีบักบน. net เวอร์ชันก่อน 4 เหตุผลก็คือคอมไพเลอร์ C # แทรก NOP ระหว่างMonitor.Enterและtryเพื่อให้เงื่อนไข "ตามทันที" ของ JIT ถูกละเมิด
CodesInChaos

6

ล็อคของคุณจะได้รับการปลดอย่างถูกต้อง การlockกระทำเช่นนี้:

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}

และfinallyรับประกันว่าบล็อกจะดำเนินการไม่ว่าคุณจะออกจากtryบล็อกอย่างไร


อันที่จริงแล้วไม่มีการ "รับประกันรหัส" ในการดำเนินการ (เช่นคุณสามารถดึงสายไฟได้) และนั่นไม่ใช่ลักษณะของการล็อกใน 4.0 - ดูที่นี่
Marc Gravell

1
@MarcGravell: ฉันคิดเกี่ยวกับการใส่เชิงอรรถสองจุดเกี่ยวกับสองจุดที่แน่นอนเหล่านั้น แล้วฉันก็คิดว่ามันคงไม่สำคัญเท่าไหร่ :)
Ry-

1
@MarcGravel: ฉันคิดว่าทุกคนมักจะถือว่าไม่มีใครพูดถึงสถานการณ์ 'ดึงปลั๊ก' เนื่องจากไม่ใช่สิ่งที่โปรแกรมเมอร์สามารถควบคุมได้ :)
Vort3x

5

เพียงเพื่อเพิ่มคำตอบที่ยอดเยี่ยมของ Marc เล็กน้อย

สถานการณ์เช่นนี้เป็นสาเหตุที่ทำให้lockคีย์เวิร์ดมีอยู่ ช่วยให้นักพัฒนาตรวจสอบว่าล็อกถูกปลดล็อกในfinallyบล็อก

หากคุณถูกบังคับให้ใช้Monitor.Enter/ Exitเช่นเพื่อรองรับการหมดเวลาคุณต้องแน่ใจว่าได้โทรไปMonitor.Exitที่finallyบล็อกเพื่อให้แน่ใจว่ามีการคลายล็อกอย่างเหมาะสมในกรณีที่มีข้อยกเว้น

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