2009-09-02 82 views
38

嘿,我正在写一个网络应用程序,我在其中读取一些自定义二进制格式的数据包。我开始一个后台线程来等待传入的数据。问题是,编译器不让我把任何代码抛出(检查)异常到run()。它说:如何从java线程抛出一个检查异常?

run() in (...).Listener cannot implement run() in java.lang.Runnable; overridden method does not throw java.io.IOException

我想要异常杀死线程,并让它在父线程中的某处被捕获。这是可能实现或我必须处理线程中的每个异常

+1

看一看以下的答案: [如何捕捉从一个线程的异常] [1] [1]:http://stackoverflow.com/questions/ 6546193 /如何抓住线程中的异常 – 2013-01-01 06:14:54

回答

36

警告:如果您必须使用异常机制,则可能无法满足您的需求。

如果我正确理解你,你实际上并不需要检查异常(你已经接受了提示未检查异常的答案),那么简单的监听模式会更合适吗?

监听器可以住在父线程,而当你已经陷入子线程的检查异常,你可以简单地通知听众。

这意味着你有暴露,这将发生(通过public方法)的一种方式,并能够传递更多信息不是例外允许。但它确实意味着在父线程和子线程之间会有一个耦合(虽然是一个松散的)。这将取决于您的具体情况,这是否会比用未选中的方式包装检查的异常有好处。

下面是一个简单的例子(一些代码从另一个答案借):

public class ThingRunnable implements Runnable { 
    private SomeListenerType listener; 
    // assign listener somewhere 

    public void run() { 
     try { 
      while(iHaveMorePackets()) { 
       doStuffWithPacket(); 
      } 
     } catch(Exception e) { 
      listener.notifyThatDarnedExceptionHappened(...); 
     } 
    } 
} 

耦合来自一个物体在具有成为SomeListenerType类型的父线程。

+0

好主意。是的,你是对的,我不在乎是否检查异常,我只是想要一个简单的方法来引发异常。 – mik01aj 2009-09-02 21:18:14

+0

侦听器代码是否仍然实际在子线程中运行?我知道它现在在父线程中有很多范围,但它仍然在子线程中运行。 – Jared 2009-12-18 15:50:54

+0

它会在子线程中运行,但它与要求的内容相匹配,我想。 – Grundlefleck 2009-12-18 18:51:29

3

如果在引发异常时确实无法做任何有用的事情,可以将检查的异常包装在RuntimeException中。

try { 
    // stuff 
} catch (CheckedException yourCheckedException) { 
    throw new RuntimeException("Something to explain what is happening", yourCheckedException); 
} 
+4

在抛出异常时总是添加说明,即使它只是要打包。 抛出新的RuntimeException(“包装异常,让它冒泡到foo.bar.Main()”,e)中的捕手。 当代码中断时,在凌晨3点调用的那些将在他们盯着堆栈跟踪时感激它。 – 2009-09-02 19:06:22

1

如果您的线程代码抛出RuntimeExpection,则不需要添加run()throw Exception。

但使用此解决方案只有在适当的时候,因为这可能是一个坏的初步实践: http://java.sun.com/docs/books/tutorial/essential/exceptions/runtime.html

任何RuntimeException的或未经检查的异常可以帮助你。也许你需要创建自己的RuntimeException

+2

请注意,此方法将隐藏父线程的异常。 – erickson 2009-09-02 18:15:07

3

线程不能将异常抛出到任何其他线程(或主线程)。你不能让继承的run()方法抛出任何检查的异常,因为你只能抛出少于继承的代码,而不是更多。

+0

关于不能直接将异常“抛出”到父线程的好处。 – erickson 2009-09-02 18:16:14

0

在你的代码是在某种循环的假设,你会写:

public class ThingRunnable implements Runnable { 
    public void run() { 
    try { 
     while(iHaveMorePackets()) { 
     doStuffWithPacket() 
     } 
    } catch(Exception e) { 
     System.out.println("Runnable terminating with exception" + e); 
    } 
    } 
} 

异常将自动中断你出你的循环,并在运行结束()方法,线程将停止。

+0

小的观点:你的例子中有一个实现的接口,应该是“public class ThingRunnable implements Runnable”。 – Grundlefleck 2009-09-02 20:14:00

+0

谢谢,Grundlefleck,你绝对是对的:-)看看在你有足够的咖啡之前回答问题会发生什么。我已将文字更新为“班”。 – AlBlue 2009-09-03 08:29:43

7

我所做的是在线程中捕获异常并将其存储为Runnable的成员变量。这个异常然后通过Runnable上的getter公开。然后,我扫描父级的所有线程,看看是否有异常,并采取适当的措施。

+1

我可以问(如果我的回答错误),为什么要将变量存储为getter并扫描它而不是使用侦听器机制? – Grundlefleck 2009-09-02 20:15:22

+0

公平的问题。这两种方法都有效。我想我会在下次尝试你的建议,看看我是否喜欢这样。 – 2009-09-02 20:20:10

+0

看到我对Grundlefleck的答案的评论 - 我相信这个解决方案确实将异常处理恢复回到父线程,而Grundlefleck的解决方案不会。 (Grundlefleck修复了范围问题 - 但是这个修复了与线程上下文真正相关的问题。) – Jared 2009-12-18 15:51:52

45

为了能够将异常发送到父线程,可以将您的后台线程放在Callable(它允许抛出异常),然后将其传递给some Executorsubmit方法。提交方法将返回一个Future,然后您可以使用该方法来获取该异常(其get方法将抛出包含原始异常的ExecutionException)。

+3

这对我来说看起来太复杂了。但是,无论如何,谢谢:) – mik01aj 2009-09-02 21:21:16

31

此答案基于Esko Luontola,但它提供了一个工作示例。

与Runnable接口的run()方法不同,Callable的call()方法允许抛出一些异常。下面是一个实现的例子:

public class MyTask implements Callable<Integer> { 

    private int numerator; 
    private int denominator; 

    public MyTask(int n, int d) { 
     this.numerator = n; 
     this.denominator = d; 
    } 

    @Override 
    // The call method may throw an exception 
    public Integer call() throws Exception { 
     Thread.sleep(1000); 
     if (denominator == 0) { 
      throw new Exception("cannot devide by zero"); 
     } else { 
      return numerator/denominator; 
     } 
    } 

} 

执行人提供了一种机制,以一个线程中运行一个可调用和处理任何类型的异常:

public class Main { 

    public static void main(String[] args) { 

     // Build a task and an executor 
     MyTask task = new MyTask(2, 0); 
     ExecutorService threadExecutor = Executors.newSingleThreadExecutor(); 

     try { 
      // Start task on another thread 
      Future<Integer> futureResult = threadExecutor.submit(task); 

      // While task is running you can do asynchronous operations 
      System.out.println("Something that doesn't need the tasks result"); 

      // Now wait until the result is available 
      int result = futureResult.get(); 
      System.out.println("The result is " + result); 
     } catch (ExecutionException e) { 
      // Handle the exception thrown by the child thread 
      if (e.getMessage().contains("cannot devide by zero")) 
       System.out.println("error in child thread caused by zero division"); 
     } catch (InterruptedException e) { 
      // This exception is thrown if the child thread is interrupted. 
      e.printStackTrace(); 
     } 
    } 
} 
+6

这解决了未捕获异常的问题,但可能会引入它自己的问题。调用get()会阻塞,直到可调用任务返回。这意味着你不再从后台线程中获得任何并行性。更糟糕的是,如果任务是连续/长时间运行的(例如在循环中等待网络数据包),那么'Callable'将不会返回,并且您的主线程将永久阻塞。 我并不是说暗示这种模式没有意义(我确定有很多),只是需要小心。 – theisenp 2013-03-28 20:28:20

+0

好点。在一个真实的例子中,你不会在'submit'之后调用'get'。 – 2013-03-30 21:23:39

+0

在实际的例子中,你可以并行地启动N个后台操作,并等待M <= N的M个后台操作的结果做出决定。在这种情况下,您会在提交后立即致电。 – Ameliorator 2015-04-06 13:03:19

-1

包装一下你的例外内RuntimeException似乎做的伎俩。

someMethod() throws IOException 
{ 
    try 
    { 
     new Thread(() -> 
     { 
      try 
      { 
       throw new IOException("a checked exception thrown from within a running thread"); 
      } 
      catch(IOException ex) 
      { 
       throw new RuntimeException("a wrapper exception", ex); // wrap the checked exception inside an unchecked exception and throw it 
      } 
     }).start(); 
    } 
    catch(RuntimeException ex) // catch the wrapped exception sent from within the thread 
    { 
     if(ex.getCause() instanceof IOException) 
      throw ex.getCause; // unwrap the checked exception using getCause method and use it however you need 
     else 
      throw ex; 
    } 
} 
+0

它不起作用 – Richard 2017-03-25 13:24:53