Một đối tượng bị khóa có bị khóa không nếu một ngoại lệ xảy ra bên trong nó?


87

Trong ứng dụng ac # threading, nếu tôi khóa một đối tượng, chúng ta hãy nói một hàng đợi và nếu một ngoại lệ xảy ra, đối tượng có bị khóa không? Đây là mã giả:

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

Theo tôi hiểu, mã sau khi bắt không thực thi - nhưng tôi đã tự hỏi liệu khóa có được giải phóng hay không.


1
Theo suy nghĩ cuối cùng (xem các bản cập nhật) - bạn có thể chỉ nên giữ khóa trong khoảng thời gian dequeue ... thực hiện xử lý bên ngoài khóa.
Marc Gravell

1
Mã sau khi bắt thực thi vì ngoại lệ được xử lý
cjk

Cảm ơn Tôi chắc đã bỏ lỡ câu hỏi đó, tôi có nên xóa câu hỏi này không?
Vort3x

4
Có vẻ như mã mẫu không tốt cho câu hỏi này, nhưng câu hỏi khá hợp lệ.
SalvadorGomez

Bởi C # Designer - Lock & Exception
Jivan

Câu trả lời:


90

Đầu tiên; bạn đã coi TryParse chưa?

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

Khóa sẽ được phát hành vì 2 lý do; đầu tiên, lockvề cơ bản là:

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

Thứ hai; bạn nắm bắt và không ném lại ngoại lệ bên trong, vì vậy lockkhông bao giờ thực sự thấy ngoại lệ. Tất nhiên, bạn đang giữ khóa trong thời gian của MessageBox, điều này có thể là một vấn đề.

Vì vậy, nó sẽ được phát hành trong tất cả các trường hợp ngoại lệ không thể phục hồi thảm họa chết người nhất.


14
Tôi biết về tryparse, nhưng nó không thực sự liên quan đến câu hỏi của tôi. Đây là mã đơn giản để giải thích câu hỏi - không phải là mối quan tâm thực sự về phân tích cú pháp. Vui lòng thay thế phân tích cú pháp bằng bất kỳ mã nào sẽ bắt buộc và khiến bạn thoải mái.
Khadaji

13
Làm thế nào về ném Exception mới ("cho mục đích minh họa"); ;-p
Marc Gravell

1
Ngoại trừ trường hợp TheadAbortExceptionxảy ra giữa Monitor.Entertry: blogs.msdn.com/ericlippert/archive/2009/03/06/…
Igal Tabachnik

2
"thảm họa chết người ngoại lệ không thể phục hồi" như băng qua suối.
Phil Cooper

98

Tôi lưu ý rằng không ai đã đề cập trong câu trả lời của họ cho câu hỏi cũ này rằng việc giải phóng khóa khi có ngoại lệ là một điều cực kỳ nguy hiểm. Có, các câu lệnh khóa trong C # có ngữ nghĩa "cuối cùng"; khi điều khiển thoát ra khỏi khóa bình thường hoặc bất thường, khóa sẽ được giải phóng. Tất cả các bạn đang nói về điều này như thể nó là một điều tốt, nhưng nó là một điều xấu! Điều đúng đắn cần làm nếu bạn có một vùng bị khóa tạo ra một ngoại lệ không được xử lý là chấm dứt quá trình bị bệnh ngay lập tức trước khi nó phá hủy nhiều dữ liệu người dùng hơn , không giải phóng khóa và tiếp tục .

Hãy nhìn nó theo cách này: giả sử bạn có một phòng tắm với khóa cửa và một dòng người đang đợi bên ngoài. Một quả bom trong phòng tắm nổ tung, giết chết một người trong đó. Câu hỏi của bạn là "trong tình huống đó khóa sẽ tự động mở để người tiếp theo có thể vào phòng tắm?" Nó sẽ được thôi. Đó không phải là một điều tốt. Một quả bom đã nổ trong đó và giết một người! Hệ thống ống nước có thể đã bị phá hủy, ngôi nhà không còn chắc chắn về mặt cấu trúc, và có thể có một quả bom khác trong đó . Việc cần làm là đưa mọi người ra ngoài càng nhanh càng tốt và phá dỡ toàn bộ ngôi nhà.

Ý tôi là, hãy suy nghĩ kỹ: nếu bạn đã khóa một vùng mã để đọc từ cấu trúc dữ liệu mà nó không bị biến đổi trên một luồng khác và một cái gì đó trong cấu trúc dữ liệu đó đã đưa ra một ngoại lệ, thì rất tốt đó là do cấu trúc dữ liệu là tham nhũng . Dữ liệu người dùng bây giờ bị rối tung; bạn không muốn cố gắng lưu dữ liệu người dùng tại thời điểm này vì khi đó bạn đang lưu dữ liệu bị hỏng . Chỉ cần chấm dứt quá trình.

Nếu bạn đã khóa một vùng mã để thực hiện đột biến mà không có luồng khác đọc trạng thái đồng thời và đột biến ném, thì nếu dữ liệu không bị hỏng trước đây, thì chắc chắn là bây giờ . Đó chính xác là kịch bản mà khóa được cho là để bảo vệ chống lại . Bây giờ mã đang chờ đọc trạng thái đó sẽ ngay lập tức được cấp quyền truy cập vào trạng thái bị hỏng, và có thể chính nó bị sập. Một lần nữa, điều đúng đắn cần làm là chấm dứt quá trình.

Bất kể bạn cắt nó như thế nào, một ngoại lệ bên trong ổ khóa là một tin xấu . Câu hỏi phù hợp để hỏi không phải là "ổ khóa của tôi có được dọn dẹp trong trường hợp ngoại lệ không?" Câu hỏi phù hợp cần đặt ra là "làm cách nào để đảm bảo rằng không bao giờ có ngoại lệ bên trong khóa? Và nếu có, thì làm cách nào để cấu trúc chương trình của mình để các đột biến được khôi phục về trạng thái tốt trước đó?"


19
Vấn đề đó khá trực quan với việc khóa IMO. Nếu bạn nhận được một ngoại lệ dự kiến, bạn muốn dọn dẹp mọi thứ, bao gồm cả ổ khóa. Và nếu bạn nhận được một ngoại lệ không mong muốn, bạn có vấn đề, có hoặc không có ổ khóa.
CodesInChaos

10
Tôi nghĩ rằng tình huống được mô tả ở trên là một chủ nghĩa chung chung. Đôi khi ngoại lệ mô tả các sự kiện thảm khốc. Đôi khi họ không. Mỗi người sử dụng chúng khác nhau trong mã. Hoàn toàn hợp lệ khi ngoại lệ là tín hiệu của một sự kiện ngoại lệ nhưng không thảm khốc - giả sử trường hợp ngoại lệ = thảm họa, trường hợp kết thúc quy trình là quá cụ thể. Thực tế có thể là một sự kiện thảm khốc không loại bỏ tính hợp lệ của câu hỏi - cùng một luồng suy nghĩ có thể khiến bạn không bao giờ xử lý bất kỳ ngoại lệ nào, trong trường hợp đó quy trình sẽ bị loại bỏ ...
Gerasimos R

1
@GerasimosR: Thật vậy. Hai điểm cần nhấn mạnh. Đầu tiên, các trường hợp ngoại lệ nên được coi là thảm họa cho đến khi được xác định là lành tính. Thứ hai, nếu bạn nhận được một ngoại lệ lành tính bị ném ra khỏi vùng bị khóa thì vùng bị khóa có thể được thiết kế tồi; nó có lẽ đang làm quá nhiều công việc bên trong ổ khóa.
Eric Lippert

1
Tất cả đều tôn trọng, nhưng có vẻ như bạn đang nói với ông Lippert rằng thiết kế C # của khối khóa dưới mui xe sử dụng một tuyên bố cuối cùng là một điều tồi tệ. Thay vào đó, chúng ta nên làm hỏng hoàn toàn mọi ứng dụng từng có ngoại lệ trong câu lệnh khóa mà không bị bắt trong khóa. Có thể đó không phải là những gì bạn đang nói, nhưng nếu vậy, tôi thực sự hạnh phúc vì nhóm đã đi theo con đường mà họ đã làm chứ không phải con đường của bạn (cực đoan vì những lý do thuần túy!). Tất cả sự tôn trọng.
Nicholas Petersen

2
Trong trường hợp tôi không hiểu rõ ý tôi là không thể kết hợp: Giả sử chúng ta có một phương thức "chuyển" lấy hai danh sách, s và d, khóa s, khóa d, xóa một mục khỏi s, thêm mục vào d, mở khóa d, mở khóa s. Phương pháp này chỉ đúng nếu không có ai bao giờ cố gắng để chuyển từ danh sách X vào danh sách Y cùng lúc như những nỗ lực khác một người nào đó để chuyển từ Y đến X . Tính đúng đắn của phương pháp chuyển giao không cho phép bạn xây dựng một giải pháp chính xác cho một vấn đề lớn hơn từ nó, bởi vì khóa là các đột biến không an toàn của trạng thái toàn cầu. Để "chuyển giao" một cách an toàn, bạn phải biết về mọi khóa trong chương trình.
Eric Lippert

43

vâng, điều đó sẽ phát hành đúng cách; lockhoạt động như try/ finally, với Monitor.Exit(myLock)cuối cùng, vì vậy bất kể bạn thoát bằng cách nào, nó sẽ được phát hành. Như một lưu ý phụ, catch(... e) {throw e;}tốt nhất là nên tránh, vì điều đó làm hỏng dấu vết ngăn xếp trên e; nó là tốt hơn không bắt nó ở tất cả , hoặc cách khác: sử dụng throw;chứ không phải throw e;mà hiện một tái ném.

Nếu bạn thực sự muốn biết, khóa trong C # 4 / .NET 4 là:

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

14

"Một câu lệnh khóa được biên dịch thành một lệnh gọi tới Monitor.Enter, sau đó là một khối try ... last. Trong khối cuối cùng, Monitor.Exit được gọi.

Việc tạo mã JIT cho cả x86 và x64 đảm bảo rằng việc hủy bỏ luồng không thể xảy ra giữa một lệnh gọi Monitor.Enter và một khối thử ngay sau đó. "

Lấy từ: Trang web này


1
Có ít nhất một trường hợp điều này không đúng: Phá thai chuỗi trong chế độ gỡ lỗi trên các phiên bản .net trước 4. Lý do là trình biên dịch C # chèn một NOP giữa dấu Monitor.Entertryđể điều kiện "ngay sau đó" của JIT bị vi phạm.
CodesInChaos

6

Khóa của bạn sẽ được phát hành đúng cách. A lockhoạt động như thế này:

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

finallycác khối được đảm bảo sẽ thực thi, bất kể bạn rời trykhối bằng cách nào .


Trên thực tế, không cónào được "đảm bảo" để thực thi (ví dụ: bạn có thể kéo cáp nguồn) và đó không phải là ổ khóa trông như thế nào trong 4.0 - xem tại đây
Marc Gravell

1
@MarcGravell: Tôi đã nghĩ đến việc đặt hai chú thích cuối trang về hai điểm chính xác đó. Và sau đó tôi nghĩ rằng nó sẽ không quan trọng lắm :)
Ry-

1
@MarcGravel: Tôi nghĩ rằng tất cả mọi người luôn luôn giả định ai là không nói về một 'rút phích cắm' tình hình, vì nó không phải là một cái gì đó một lập trình viên có bất kỳ kiểm soát :)
Vort3x

5

Chỉ để thêm một chút vào câu trả lời xuất sắc của Marc.

Những tình huống như thế này là lý do chính cho sự tồn tại của locktừ khóa. Nó giúp các nhà phát triển đảm bảo khóa được phát hành trong finallykhối.

Nếu bạn buộc phải sử dụng Monitor.Enter/ Exitví dụ: để hỗ trợ thời gian chờ, bạn phải đảm bảo thực hiện lệnh gọi đến Monitor.Exittrong finallykhối để đảm bảo khóa đúng cách trong trường hợp có ngoại lệ.

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.