好的,我取得了一些突破,发现了两个问题:
- 的调用
_currentOutputRequest.queue(_outputBuffer, _maxPacketSize);
传递了整个缓冲区容量_maxPacketSize
,该容量的常量值为64(字节)。显然,这仅适用于批量读取,最多可以读取64个字节。批量发送请求需要指定要发送的确切字节数。 - 的
_currentOutputRequest.queue()
和_connection.requestWait()
方法调用似乎不是线程安全的,特别是在执行UsbDeviceConnection
(其是所述类型的_connection
)。我怀疑UsbRequest_currentOutputRequest
在排队发送请求时在内部使用UsbConnection对象。
我如何解决这个问题:
的呼叫
queue()已更改为:
_currentOutputRequest.queue(_outputBuffer, _outputBuffer.position());
对于第二个问题,该
queue()语句已经在
synchronized使用该
_outputLock对象的块内发生。在
Run()阅读器线程的方法中,我还必须使用以下命令将调用包装到
requestWait()一个
synchronized块中
outputLock:
UsbRequest request = null;synchronized(_outputLock){ // requestWait() and request.queue() appear not to be thread-safe. request = _connection.requestWait();}鉴于这种情况发生在
while循环中并
requestWait阻塞了线程,我发现的一个主要问题是,当试图将发送请求排队时,锁会导致饥饿。结果是,当应用程序及时接收传入的MIDI数据并对其进行操作时,输出的MIDI事件会大大延迟。
作为此问题的部分解决方案,我
yield在
while循环结束前插入了一条语句以使UI线程保持畅通。(UI线程被暂时用作注释事件,因为它是通过按下按钮触发的;它最终将使用单独的播放线程。)因此,它更好,但并不完美,因为在第一次输出注释之前还有相当长的延迟已发送。
更好的解决方案:
为了解决对异步读取和写入的互锁的需求,异步
queue()和
requestWait()方法仅用于读取操作,这些操作保留在单独的“读取器”线程上。因此,该
synchronized块是不必要的,因此可以将该段缩减为以下内容:
UsbRequest request = _connection.requestWait();
至于写/发送操作,其核心已移至用于执行同步
bulkTransfer()语句的单独线程:
private class MidiSender extends Thread { private boolean _raiseStop = false; private Object _sendLock = new Object(); private linkedList<ByteBuffer> _outputQueue = new linkedList<ByteBuffer>(); public void queue(ByteBuffer buffer) { synchronized(_sendLock) { _outputQueue.add(buffer); // Thread will most likely be paused (to save CPU); need to wake it _sendLock.notify(); } } public void raiseStop() { synchronized (this) { _raiseStop = true; } //Thread may be blocked waiting for a send synchronized(_sendLock) { _sendLock.notify(); } } public void run() { while (true) { synchronized (this) { if (_raiseStop) return; } ByteBuffer currentBuffer = null; synchronized(_sendLock) { if(!_outputQueue.isEmpty()) currentBuffer =_outputQueue.removeFirst(); } while(currentBuffer != null) { // Here's the synchronous equivalent (timeout is a reasonable 0.1s): int transferred = _connection.bulkTransfer(_outPort, currentBuffer.array(), currentBuffer.position(), 100); if(transferred < 0) Log.w(_tag, "Failed to send MIDI packet"); //Process any remaining packets on the queue synchronized(_sendLock) { if(!_outputQueue.isEmpty()) currentBuffer =_outputQueue.removeFirst(); else currentBuffer = null; } } synchronized(_sendLock) { try { //Sleep; save unnecessary processing _sendLock.wait(); } catch(InterruptedException e) { //Don't care about being interrupted } } } } }起初,我担心异步代码与上述代码冲突(因为它们共享相同的
UsbDeviceConnection),但这似乎不是问题,因为它们使用的是完全不同的
UsbEndpoint实例。
好消息是,在我弹奏键盘的同时发送笔记时,应用程序运行更流畅且不会崩溃。
因此,一般而言(对于在单独端点上的双向USB通信),异步方法似乎最适合于读/输入操作,在这种情况下,我们无需担心定义轮询行为(无论这是否在内部发生)
),而同步
bulkTransfer()方法可以更好地服务于输出/发送操作。



