如果“x”时间已过,则创建一个取消 InputStream.read() 调用的线程

Posted

技术标签:

【中文标题】如果“x”时间已过,则创建一个取消 InputStream.read() 调用的线程【英文标题】:Making a thread which cancels a InputStream.read() call if 'x' time has passed 【发布时间】:2012-12-10 23:53:35 【问题描述】:

我目前有一个来自 android 的 BluetoothChat Example 的有效 I/O 流,但遇到了问题。我的应用程序通过蓝牙连接到蓝牙模块,蓝牙模块又向模块物理连接的设备发送信号。

我的程序在输入流上调用read(),如果有数据正在发送,程序将顺利执行,没有任何问题。但是,流的实现方式没有针对中断连接的保护。如果模块从设备中被物理移除,或者设备没有发回任何信号,我的代码只是在InputStream.read() 调用中等待。

我的read() 电话如下所示:

try 
    Log.i( "1) I/O", "available bits: " + mmInStream.available() );
    bytes = mmInStream.read(buffer, 0, length);
    Log.i( "2) I/O", "available bits: " + mmInStream.available() );
    mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
 catch (Exception e) 
    Log.i(TAG,  "Catch Statement" );
    Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST);
    Bundle bundle = new Bundle();
    bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." );
    msg.setData(bundle);
    mHandler.sendMessage(msg);
    Log.e(TAG, "disconnected a", e);
    connectionLost();

    // Start the service over to restart listening mode
    BluetoothService.this.start();
    //break;

当我的程序正确运行时,try 块中的两个Log 调用都返回0 的值mmInStream.available()。当输入流被中断时,最初的Log 调用返回一个0,第二个永远不会被调用。然后,我的程序在每次到达 catch 块之前就崩溃了。

我已经找了几天来解决这个问题,并找到了许多解决方案,但它们要么不起作用,要么我不明白。

1) 对 InputStream 使用扫描仪如下所示。这没有提供任何帮助,并且在阅读时也会超时。

Scanner scan = new Scanner(new InputStreamReader(mmInStream));
scan.useDelimiter( "[\\r\\n]+" );
String readIn;

try 
    readIn = scan.next();
    scan = null;
    tempB = readIn.getBytes( Charset.forName( "US-ASCII" ) );
    append = "\r\n".getBytes( Charset.forName( "US-ASCII" ) );
    for( int i = 0; i < length; i++ ) 
        if( i == length - 1 ) 
            buffer[i] = append[1];
         else if ( i == length - 2 ) 
            buffer[i] = append[0];
         else 
            buffer[i] = tempB[i];
        
    
    mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
 catch (Exception e) 
    Log.i(TAG,  "Catch Statement" );
                Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST);
                Bundle bundle = new Bundle();
                bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." );
                msg.setData(bundle);
                mHandler.sendMessage(msg);
                Log.e(TAG, "disconnected a", e);
                connectionLost();

                // Start the service over to restart listening mode
                BluetoothService.this.start();
                //break;
            

2) 我尝试运行一个线程,它会在 X 时间后取消 read 调用,但它无法正常工作:

public void run(int length) throws IOException 
    buffer = new byte[1024];
    length1 = length;
    Thread myThread = new Thread(new Runnable() 
        public void run() 
            try 
                bytes = mmInStream.read( buffer, 0, length1 );
             catch (IOException e) 
                // TODO Auto-generated catch block
                e.printStackTrace();
            
        
    );

    synchronized (myThread) 
        myThread.start();
        try 
            myThread.wait(500);
            if(myThread.isAlive()) 
                mmInStream.close();
                Log.i( "InStream", "Timeout exceeded!");
            
         catch (InterruptedException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        

    
   try 
        myThread.run();
        mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer)
                    .sendToTarget();
    catch (IOException e) 
            Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST);
            Bundle bundle = new Bundle();
            bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." );
            msg.setData(bundle);
            mHandler.sendMessage(msg);
            connectionLost();
            BluetoothService.this.start();
   

在这两个选项不起作用后,我一直在尝试查看 Java NIOAsyncTask,但所有这些似乎都需要添加太多东西来识别 I/O 超时。我还看到一些Sockets 支持使用.setSoTimeout() 的超时功能,但这是BluetoothSocket,据我发现他们不支持此功能。

由于没有I/O 类支持将超时长度作为参数的read() 方法,或者根本没有超时,在我看来,添加线程将是最简单的实现。这是错的吗?任何有关我在上述方法中做错了什么或如何合并Java NIO/AsyncTask 的信息将不胜感激。

编辑:

这是我尝试的新线程代码,我目前正在将其更改为给定答案显示的内容并尝试。如果以后不行我会发帖的。

Thread myThread = new Thread(new Runnable() 
            public void run() 
                try 
                    bytes = mmInStream.read( buffer, 0, length1 );
                 catch (IOException e) 
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                
            
        );

        synchronized (myThread) 
            try 
                myThread.wait(6000);
                Log.i( "InStream", "After wait" );
                if(myThread.isAlive()) 
                    Log.i( "InStream", "Timeout exceeded2!");
                    myThread.interrupt();
                    Log.i( "InStream", "Timeout exceeded!");
                 else 
                    myThread.interrupt();
                
             catch (InterruptedException e) 
                // TODO Auto-generated catch block
                Log.i( "InStream", "Exception Caught" );
                e.printStackTrace();
            

        

编辑 2:

我已经尝试过Dheerej 在下面给出的答案。我在wait() 函数调用中得到了IllegalMonitorStateException。我尝试了答案中显示的内容,然后还尝试了myThread.wait() 而不是Thread.currentThread.wait()。我假设正在引发此异常,因为这是 myThread 对象正在创建并在另一个线程中运行。无论如何,下面的代码几乎与Dheerej's 答案相同。

        int length1 = length;
            Thread myThread = new Thread(new Runnable() 
                public void run() 
                    buffer = new byte[1024];
                    try 
                        bytes = mmInStream.read(buffer, 0, length1);
                     catch (IOException e) 
                        e.printStackTrace();
                    
                    mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer)
                                .sendToTarget();
                
            );

            myThread.start();
            try 
                //Thread.currentThread().wait(500);
                myThread.wait( 1000 );              // Line 533
             catch (InterruptedException e) 
                e.printStackTrace();
                //Log.i(TAG,  "Catch Statement" );
                Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST);
                Bundle bundle = new Bundle();
                bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." );
                msg.setData(bundle);
                mHandler.sendMessage(msg);
                Log.e(TAG, "disconnected a", e);
                connectionLost();

                // Start the service over to restart listening mode
                BluetoothService.this.start();
            

            if (myThread.isAlive()) 
                mmInStream.close(); // Alternatively try: myThread.interrupt()
            

这是生成的 LogCat。错误说它从第 533 行开始,也就是上面的 wait() 调用:

12-28 17:44:18.765: D/BLZ20_WRAPPER(3242): blz20_wrp_poll: return 1
12-28 17:44:18.765: D/BLZ20_WRAPPER(3242): blz20_wrp_write: wrote 3 bytes out of 3 on fd 62
12-28 17:44:18.769: W/NATIVE CODE(3242): -4) baud9600=1, goodbaud=1
12-28 17:44:18.769: D/AndroidRuntime(3242): Shutting down VM
12-28 17:44:18.769: W/dalvikvm(3242): threadid=1: thread exiting with uncaught exception (group=0x40015578)
12-28 17:44:18.773: E/AndroidRuntime(3242): FATAL EXCEPTION: main
12-28 17:44:18.773: E/AndroidRuntime(3242): java.lang.IllegalMonitorStateException: object not locked by thread before wait()
12-28 17:44:18.773: E/AndroidRuntime(3242):     at java.lang.Object.wait(Native Method)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at java.lang.Object.wait(Object.java:395)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at my.eti.commander.BluetoothService$ConnectedThread.run(BluetoothService.java:533)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at my.eti.commander.BluetoothService.read(BluetoothService.java:326)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at my.eti.commander.BluetoothService.changeitJava(BluetoothService.java:669)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at my.eti.commander.RelayAPIModel$NativeCalls.changeItJavaWrapper(RelayAPIModel.java:490)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at my.eti.commander.RelayAPIModel$NativeCalls.InitRelayJava(Native Method)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at my.eti.commander.MainMenu$1.handleMessage(MainMenu.java:547)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at android.os.Handler.dispatchMessage(Handler.java:99)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at android.os.Looper.loop(Looper.java:130)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at android.app.ActivityThread.main(ActivityThread.java:3687)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at java.lang.reflect.Method.invokeNative(Native Method)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at java.lang.reflect.Method.invoke(Method.java:507)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
12-28 17:44:18.773: E/AndroidRuntime(3242):     at dalvik.system.NativeStart.main(Native Method)
12-28 17:44:18.781: D/BLZ20_ASOCKWRP(3242): asocket_read
12-28 17:44:18.781: I/BLZ20_WRAPPER(3242): blz20_wrp_poll: nfds 2, timeout -1 ms
12-28 17:44:18.890: D/BLZ20_WRAPPER(3242): blz20_wrp_poll: transp poll : (fd 62) returned r_ev [POLLIN ] (0x1)
12-28 17:44:18.890: D/BLZ20_WRAPPER(3242): blz20_wrp_poll: return 1
12-28 17:44:18.890: D/BLZ20_WRAPPER(3242): blz20_wrp_read: read 5 bytes out of 5 on fd 62

【问题讨论】:

在过去几周使用 BluetoothChat 示例后,我只是一个疯狂的猜测:我认为输入流有一个 isAvailable 方法。也许一个while循环就可以了? 谢谢,但正如我在第一个代码块中展示的那样,每次调用 .isAvailable() 时都会返回 0。即使我的代码正确执行并接收到信息,也会返回 0 为什么你在调用myThread.start()之后还要显式调用myThread.run()??等待后尝试调用myThread.interrupt() 而不是mmInStream.close() 我不会在start() 之后打电话给run()。我打电话给run(),然后在synchronized 打电话给start()。那很可能是错误的,但它与您阅读的内容不同。我很快就会试试这个。谢谢 线程实现不正确。您应该在 run() 方法中等待。目前,一旦 run() 结束,线程生命就结束了。所以 isAlive() 永远不会返回 true。不要像上面建议的那样显式调用 run() ,如果进行了更改,请共享。至于原始代码,请尝试按照评论 1 的建议使用 isAvailable() 和 available() 。再次分享任何更改。至于没有到达 catch 块,唯一的可能性是抛出 Error() 而不是 Exception()。尝试捕获 Throwable() 仅用于调试。如果这行得通,那么重新考虑捕获异常或错误不是一个好选择。 【参考方案1】:

先试试这个:

try 
    int available = 0;

    while (true)
    
        int available = mmInStream.available();
        if (available > 0)  break; 
        Thread.sleep(1);
        // here you can optionally check elapsed time, and time out
    

    Log.i( "1) I/O", "available bits: " + available );
    bytes = mmInStream.read(buffer, 0, length);
    Log.i( "2) I/O", "available bits: " + mmInStream.available() );
    mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
 catch (Exception e) 
    ...

在您的原始代码中,您在read() 之前调用available(),通常没有数据等待读取。然后你调用read(),它会阻塞并等待数据,然后读取所有数据。然后你再次调用available(),再次没有数据,因为它已经全部被读取:) 更好:睡眠直到available() 返回非零值,然后读取。但是,这可能不起作用,因为available()总是允许返回 0(即使数据实际可用)。

如果上述方法不起作用,请尝试以下问题中的技术:Is it possible to read from a InputStream with a timeout?

Callable<Integer> readTask = new Callable<Integer>() 
    @Override
    public Integer call() throws Exception 
        return mmInStream.read(buffer, 0, length);
    


try 
    Future<Integer> future = executor.submit(readTask);
    bytes = future.get(100, TimeUnit.MILLISECONDS);
    mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
 catch (TimeoutException e) 
    // deal with timeout in the read call
 catch (Exception e) 
    ...

最后,BluetoothSocket 文档说您可以从任何线程关闭套接字并立即生效。所以你可以简单地有一个看门狗线程,如果读取调用没有成功调用套接字上的close(),这将导致阻塞的read()返回错误。这是 Dheeraj 上面建议的,但您只需要在另一个线程卡住(由于网络错误/连接丢失/等)时调用 close():否则只需偶尔检查其进度但不要关闭为只要您的阅读时间不会太长。

很长时间里,似乎没有超时(以及不可能从外部中断阻塞的 read())一直是 Java 的主要痛点。

另见:

Is it possible to read from a InputStream with a timeout?(使用Callable/Future

Can I set a timeout for a InputStream's read() function?(使用Socket.setSoTimeout()

How to kill a BufferedInputStream .read() call(使用InterruptibleChannel

How to stop a thread waiting in a blocking read operation in Java?

【讨论】:

对不起,这几天我一直不在,现在正在阅读。非常感谢。 你给我的第一个实现很完美。谢谢。 伙计们,如果你不介意我问的话。我对此有点困惑,我尝试了第一个实现,它只是像永远一样停留在 while 循环中。它没有进入bytes = mmInStream.read(buffer, 0, length); 部分 @IssacZH.:一些 InputStream 实现总是从 available() 调用返回 0。流也可能没有任何数据可供读取。无论返回值如何,您都尝试阅读它吗?您使用的是哪种流?尝试一些简单的方法,例如 ByteArrayInputStream 或 FileInputStream。【参考方案2】:

试试这个扩展我上面评论的代码:

public void run(final int length) 
    Thread myThread = new Thread(new Runnable() 
        public void run() 
            buffer = new byte[1024];
            try 
                bytes = mmInStream.read(buffer, 0, length);
             catch (IOException e) 
                e.printStackTrace();
            
            mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
        
    );

    myThread.start();
    try 
        Thread.sleep(500);
     catch (InterruptedException e) 
        e.printStackTrace();
    

    if (myThread.isAlive()) 
        mmInStream.close(); // Alternatively try: myThread.interrupt()
    

【讨论】:

我更新了我的代码并用结果编辑了我的问题。我尝试使用您编写的内容,然后尝试使用myThread.interrupt() 而不是mmInstream.close(),然后将Thread.currentThread().wait(500) 切换为myThread.wait(500)。然后我切换回mmInStream.close,但在包含wait() 调用的行中出现了错误。另外,我不确定这是否重要,但这个线程正在被实例化并在另一个线程中运行。 @JuiCe 使用了sleep()。我一定一直在睡觉()没有注意到这一点:-) 好吧,这样就可以了。我可以通过我的第一组读/写命令。但在那之后,它就深深地卡在了我的代码中。除了mmInStream.close(),我还有什么可以做的吗,因为我稍后会再次使用它? @JuiCe 否。您可能必须考虑同步线程以避免竞争条件。如果遇到困难,请使用相关代码发布另一个问题。 尽可能将InputStream 对象保持在本地。在稍后使用之前检查它是否已关闭。

以上是关于如果“x”时间已过,则创建一个取消 InputStream.read() 调用的线程的主要内容,如果未能解决你的问题,请参考以下文章

解决xshell评估期已过的问题

如果 PHP 中的键以 X 开头,则删除 cookie

如果触发时间已过,请勿在应用启动时触发触发器。石英

VB SaveFileDialog 如果取消则

正版SQL2008r2 提示评估期已过怎么办

如果移出 CGPoint 则取消触摸