2012-06-27 27 views
9

tôi đã đi qua đoạn code dưới đây, và tôi tự hỏi nếu nó thực hiện chính xác những gì tôi nghĩ nó:Xử lý InterruptedException trong khi chờ đợi một tín hiệu lối ra (bug trong Android?)

synchronized(sObject) { 
    mShouldExit = true; 
    sObject.notifyAll()  
    while (!mExited) { 
     try { 
      sObject.wait(); 
     } catch (InterruptedException ex) { 
      Thread.currentThread().interrupt(); 
     } 
    } 
} 

Về ngữ cảnh: có một luồng khác kiểm tra mShouldExit (bên trong màn hình sObject) và sẽ thoát trong trường hợp đó.

Đây không phải là mẫu phù hợp với tôi. Nếu một ngắt xảy ra, nó sẽ thiết lập trạng thái bị gián đoạn một lần nữa, vì vậy khi nó trở về sObject.wait(), một InterruptedException khác sẽ đến vv .vv. Do đó, nó không bao giờ có thể chuyển sang trạng thái chờ đợi thực sự (sObject.wait()) tức là nó sẽ không bao giờ giải phóng sObject màn hình. Điều này có thể dẫn đến một vòng lặp vô hạn, vì luồng khác không thể đặt mExiting thành true, vì nó không bao giờ có thể vào màn hình của sObject. (Vì vậy, tôi nghĩ rằng các cuộc gọi interrupt() là một lỗi, nó không phải được sử dụng ở đây.) Tôi có thiếu cái gì?

Lưu ý rằng đoạn mã là một phần của mã nguồn khung chính thức của Android.

CẬP NHẬT: thực tế, tình hình tồi tệ hơn, bởi vì cùng một mẫu được sử dụng trong Android khi quá trình kết xuất GL của bạn bắt đầu. Các mã nguồn chính thức của GLSurfaceView.GLThread.surfaceCreated():

public void surfaceCreated() { 
     synchronized(sGLThreadManager) { 
      if (LOG_THREADS) { 
       Log.i("GLThread", "surfaceCreated tid=" + getId()); 
      } 
      mHasSurface = true; 
      sGLThreadManager.notifyAll(); 
      while((mWaitingForSurface) && (!mExited)) { 
       try { 
        sGLThreadManager.wait(); 
       } catch (InterruptedException e) { 
        Thread.currentThread().interrupt(); 
       } 
      } 
     } 
    } 

Bạn có thể tái tạo các lỗi trong một cách tương tự như: đảm bảo thread UI của bạn có cờ tình trạng gián đoạn nào được nêu ra, sau đó thêm GLSurfaceView bạn và bắt đầu vẽ GL (thông qua setRenderer(...), nhưng trên một số thiết bị, hãy đảm bảo rằng GLSurfaceView của bạn có trạng thái Visibility.VISIBLE, nếu không hiển thị sẽ không bắt đầu).

Nếu bạn làm theo các bước trên, thread UI của bạn sẽ kết thúc trong một vòng lặp vô hạn, vì mã trích dẫn ở trên sẽ tiếp tục tạo ra một InterruptedException (do wait()) và do đó các chủ đề GL sẽ không bao giờ có thể thiết lập mWaitingForSurface thành sai.

Theo thử nghiệm của tôi, có vẻ như một vòng lặp vô hạn như vậy cũng sẽ dẫn đến một chuỗi vô tận bộ sưu tập rác GC_CONCURRENT (hoặc, ít nhất, các thông điệp như vậy trong logcat). Thú vị, một người nào đó có một vấn đề chưa xác định rõ ràng về stackoverflow trước đó có thể liên quan: How to solve GC_concurrent freed?

Có thể chuỗi giao diện người dùng của anh ấy có cờ ngắt được đặt thành true và anh ấy đang sử dụng GLSurfaceView cho bản đồ ông đề cập đến? Chỉ là một giả định, một kịch bản có thể xảy ra.

+0

bản sao có thể có của [Xử lý gián đoạn gián đoạn trong Java] (http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java) và quá trình này: http://stackoverflow.com/questions/5915156/ how-can-i-kill-a-thread-without-using-stop/5915306 # 5915306 – assylias

+1

Không liên quan gì đến chủ đề đầu tiên bạn đã liên kết (ngoài thực tế cả hai đều liên quan đến InterruptedException). Đây là một vấn đề phức tạp hơn, và câu hỏi cũng hoàn toàn khác. (Tôi biết bạn đã viết "có thể", vì vậy tôi chỉ nói với mọi người rằng nó chắc chắn là không.) Thứ hai bạn thêm vào sau khi chỉnh sửa bình luận có liên quan nhiều hơn, nó chính xác xác nhận sự nghi ngờ của tôi rằng mã trên là sai, nhưng vì đó là nguồn Android chính thức, một câu trả lời rõ ràng từ một chuyên gia sẽ rất tuyệt. –

+0

Bạn đúng - thực tế là nó không phải là một bản sao và trên thực tế là đặt lại trạng thái bị gián đoạn sẽ làm cho chờ đợi thoát ngay lập tức. Ai đó có thể thấy một lý do chính đáng để làm điều đó ... – assylias

Trả lời

13

Phiên bản ngắn: Mã đó sai và sẽ gây ra vòng lặp vô hạn (tôi vẫn còn nghi ngờ, nhưng có thể phụ thuộc vào việc triển khai JVM). Đặt trạng thái ngắt là điều đúng để làm, nhưng sau đó nó sẽ thoát khỏi vòng lặp, cuối cùng kiểm tra trạng thái gián đoạn đó bằng cách sử dụng Thread.isInterrupted().

phiên bản dài cho người đọc bình thường:

Vấn đề là làm thế nào để ngăn chặn một chủ đề mà hiện đang thực hiện một số công việc, để đáp ứng với một nút "Hủy bỏ" từ người sử dụng hoặc vì một số logic ứng dụng khác.

Ban đầu, Java hỗ trợ phương thức "dừng", đã ngăn chặn trước một chuỗi. Phương pháp này đã được chứng minh là không an toàn, nguyên nhân không đưa ra nguyên tắc đã dừng (bất kỳ) cách nào để dọn sạch, giải phóng tài nguyên, tránh để lộ các đối tượng được sửa đổi một phần và cứ thế.

Vì vậy, Java đã phát triển thành hệ thống "gián đoạn" của chuỗi "hợp tác". Hệ thống này khá đơn giản: một Thread đang chạy, một người khác gọi là "ngắt" nó, một lá cờ được đặt trên Thread, đó là trách nhiệm của Thread để kiểm tra xem nó có bị gián đoạn hay không và hành động tương ứng.

Vì vậy, đúng Thread.run (hoặc Runnable.run, của Callable vv ..) phương pháp thực hiện, nếu có cái gì đó như:

public void run() { 
    while (!Thread.getCurrentThread().isInterrupted()) { 
    // Do your work here 
    // Eventually check isInterrupted again before long running computations 
    } 
    // clean up and return 
} 

Đây là tốt miễn là tất cả các mã đề của bạn đang thực hiện là bên trong phương thức chạy của bạn, và bạn không bao giờ gọi cái gì đó chặn trong một thời gian dài ... mà thường không phải là trường hợp, gây ra nếu bạn sinh ra một chủ đề là bởi vì bạn có một cái gì đó dài để làm.

Phương pháp đơn giản nhất chặn là Thread.sleep (millis), nó thực sự là điều duy nhất nó làm: nó chặn luồng trong một khoảng thời gian nhất định.

Bây giờ, nếu ngắt đến khi luồng của bạn nằm trong Thread.sleep (600000000), mà không có bất kỳ sự hỗ trợ nào khác, nó sẽ mất rất nhiều để đến đích mà nó kiểm tra là bị ngắt.

Thậm chí còn có các tình huống mà chuỗi của bạn sẽ không bao giờ thoát. Ví dụ, thread của bạn đang tính toán thứ gì đó và gửi kết quả tới BlockingQueue với kích thước giới hạn, bạn gọi queue.put (myresult), nó sẽ chặn cho đến khi người tiêu dùng giải phóng một số khoảng trống trong hàng đợi, nếu trong thời gian đó người tiêu dùng đã bị gián đoạn (hoặc chết hoặc bất cứ điều gì), không gian đó sẽ không bao giờ đến, phương pháp sẽ không trở lại, kiểm tra trên .isInterrupted sẽ không bao giờ được thực hiện, thread của bạn bị mắc kẹt.

Để tránh tình trạng này, tất cả (hầu hết) các phương pháp làm gián đoạn luồng (nên) ném InterruptedException. Ngoại lệ đó chỉ đơn giản là nói với bạn "Tôi đã chờ đợi điều này và điều đó, nhưng trong khi đó các chủ đề bị gián đoạn, bạn nên làm sạch và thoát càng sớm càng tốt".

Cũng như tất cả các trường hợp ngoại lệ, trừ khi bạn biết phải làm gì, bạn nên ném lại và hy vọng rằng ai đó ở trên bạn trong ngăn xếp cuộc gọi biết.

Ngắt ngoại lệ thậm chí còn tồi tệ hơn, vì khi chúng được ném "trạng thái bị gián đoạn" sẽ bị xóa. Điều này có nghĩa rằng chỉ cần bắt và bỏ qua chúng sẽ dẫn đến một chủ đề mà thường không chỉ dừng lại:

public void run() { 
    while (!Thread.getCurrentThread().isInterrupted()) { 
    try { 
     Thread.sleep(1000); 
    } catch (InterruptedException e) { 
     // Nothing here 
    } 
    } 
} 

Trong ví dụ này, nếu ngắt đến trong phương pháp ngủ() (đó là 99,9999999999% thời gian), nó sẽ ném InterruptedException, xóa cờ ngắt, sau đó vòng lặp sẽ tiếp tục vì cờ ngắt là sai, và luồng sẽ không dừng lại.

Đó là lý do tại sao nếu bạn triển khai "while" một cách chính xác, sử dụng .isInterrupted, và bạn thực sự cần phải bắt InterruptedException, và bạn không có gì đặc biệt (như dọn dẹp, trả lại vv ..) mà bạn có thể làm là đặt lại cờ ngắt.

Sự cố trong mã bạn đã đăng là "trong khi" chỉ dựa vào mExited để quyết định thời điểm dừng và không được C isNG bị gián đoạn.

while (!mExited && !Thread.getCurrentThread().isInterrupted()) { 

Hoặc nó có thể thoát ra khi bị gián đoạn:

} catch (InterruptedException e) { 
    Thread.currentThread().interrupt(); 
    return; // supposing there is no cleanup or other stuff to be done 
} 

Thiết cờ isInterrupted trở lại đúng cũng rất quan trọng nếu bạn không kiểm soát các Chủ đề.Ví dụ, nếu bạn đang ở trong một runnable đang được thực hiện trong một nhóm thread của một số loại, hoặc bên trong bất kỳ phương pháp bất cứ nơi nào bạn không sở hữu và kiểm soát thread (một trường hợp đơn giản: một servlet), bạn không biết nếu sự gián đoạn là dành cho "bạn" (trong trường hợp servlet, máy khách đóng kết nối và thùng chứa đang cố gắng ngăn bạn giải phóng luồng cho các yêu cầu khác) hoặc nếu nó được nhắm mục tiêu theo chủ đề (hoặc toàn bộ hệ thống) (container đang tắt, dừng mọi thứ).

Trong trường hợp đó (là 99% mã), nếu bạn không thể tính toán lại InterruptedException (mà, thật không may, được kiểm tra), cách duy nhất để truyền bá ngăn xếp đến nhóm luồng mà luồng đã bị gián đoạn , đang đặt cờ trở lại thành true trước khi trở về. Bằng cách đó, nó sẽ lan truyền lên ngăn xếp, cuối cùng tạo thêm InterruptedException, cho chủ sở hữu chủ đề (có thể là chính jvm, của một Executor, hoặc bất kỳ pool thread nào khác) có thể phản ứng đúng cách (sử dụng lại chủ đề), Hãy để nó chết, System.exit (1) ...)

Hầu hết điều này được đề cập trong chương 7 của Java Concurrency in Practice, một cuốn sách hay mà tôi khuyên bạn nên dành cho bất kỳ ai quan tâm đến lập trình máy tính nói chung, không chỉ Java, gây ra các vấn đề và các giải pháp tương tự trong nhiều môi trường khác, và các giải thích được viết rất tốt.

Tại sao Sun quyết định kiểm tra InterruptedException, khi hầu hết các tài liệu đề nghị không dùng lại nó một cách không thương tiếc, và tại sao họ quyết định xóa cờ bị gián đoạn khi ném ngoại lệ đó, khi điều thích hợp là đặt lại thành đúng thời gian, vẫn mở để tranh luận.

Tuy nhiên, nếu .wait giải phóng khóa TRƯỚC KHI kiểm tra cờ ngắt, nó mở một cánh cửa nhỏ từ một sợi khác để sửa đổi boolean mExited. Thật không may là phương thức wait() là native, do đó nguồn của JVM cụ thể đó cần được kiểm tra. Điều này không thay đổi thực tế là mã bạn đăng được mã hóa kém.

+1

Ai đó nên đăng nó như là một báo cáo lỗi cho Google tôi đoán. Vì mã tôi đăng là từ mã nguồn Android chính thức. Kiểm tra Grepcode cho thấy nó có mặt trong Ice Cream Sandwich mới nhất. –

+0

Có, nó nên được chỉ ra. Tôi không có kinh nghiệm với Android, nhưng cảm thấy tự do để sử dụng câu trả lời của tôi trong báo cáo lỗi của bạn nếu bạn cần. –

+0

Cảm ơn. Họ có một định dạng được xác định trước, mà cần một ví dụ làm việc để trình bày lỗi, vv, thật không may, tôi sẽ không có thời gian cho việc này trong tương lai gần. –

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