如果“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 NIO
或 AsyncTask
,但所有这些似乎都需要添加太多东西来识别 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() 调用的线程的主要内容,如果未能解决你的问题,请参考以下文章