C++、C# 和 Java 实现
C++、C# 和 Java 的 TWS API 连接结构,比 Python 更明显地把 EReader 和消息处理循环拆开。
核心模式是:
创建 EReader -> reader.start() -> 等待 signal -> reader.processMsgs()reader.start() 负责启动读取线程,把 socket 中的消息放入消息队列。reader.processMsgs() 负责从队列里取消息、解码并触发 EWrapper 回调。
和 Python 的区别
Section titled “和 Python 的区别”Python 常见写法是启动 app.run():
threading.Thread(target=app.run, daemon=True).start()C++、C# 和 Java 官方示例更常见的结构是自己创建 EReader,然后在后台线程里等待信号并调用 processMsgs()。
| 语言 | 读取线程 | 处理消息 |
|---|---|---|
| Python | EReader 在连接后由客户端创建 | app.run() 从队列取消息并解码 |
| Java | 显式创建 EReader | 等待 EReaderSignal 后调用 reader.processMsgs() |
| C# | 显式创建 EReader | 等待 EReaderSignal 后调用 reader.processMsgs() |
| C++ | 显式创建 EReader | 等待 EReaderOSSignal 后调用 processMsgs() |
它们的共同点是:连接以后必须持续处理返回消息。否则请求发出去了,回调不会正常到达。
Java 结构
Section titled “Java 结构”Java 官方示例常见结构如下:
// 创建连接客户端时传入 EWrapper 和 EReaderSignal。EReaderSignal signal = new EJavaSignal();EClientSocket client = new EClientSocket(wrapper, signal);
client.eConnect("127.0.0.1", 7497, 1);
// EReader 负责从 TWS / IB Gateway 读取消息。EReader reader = new EReader(client, signal);reader.start();
// 这个线程负责等待信号并处理消息队列。new Thread(() -> { while (client.isConnected()) { signal.waitForSignal();
try { reader.processMsgs(); } catch (Exception e) { System.out.println("处理 TWS API 消息失败: " + e.getMessage()); } }}).start();这里的 waitForSignal() 可以理解为“等待读取线程告诉我有新消息了”。processMsgs() 才是真正触发 EWrapper 回调的地方。
C# 官方示例也使用类似模式:
// signal 用来通知消息队列里有新数据。EReaderSignal signal = new EReaderMonitorSignal();EClientSocket clientSocket = new EClientSocket(wrapper, signal);
clientSocket.eConnect("127.0.0.1", 7497, 1);
var reader = new EReader(clientSocket, signal);reader.Start();
new Thread(() =>{ while (clientSocket.IsConnected()) { signal.waitForSignal(); reader.processMsgs(); }}){ IsBackground = true}.Start();如果 reader.processMsgs() 这段循环没有启动,nextValidId()、error()、tickPrice()、historicalData() 等回调都不会按预期触发。
C++ 结构
Section titled “C++ 结构”C++ 示例中常见的是 EReaderOSSignal:
EReaderOSSignal osSignal(2000);EClientSocket client(wrapper, &osSignal);
client.eConnect("127.0.0.1", 7497, 1);
std::unique_ptr<EReader> reader(new EReader(&client, &osSignal));reader->start();
while (client.isConnected()) { osSignal.waitForSignal(); reader->processMsgs();}EReaderOSSignal 的作用仍然是通知主循环:有新的消息可以处理。真正触发回调的是 reader->processMsgs()。
连接成功以后先等 nextValidId
Section titled “连接成功以后先等 nextValidId”无论使用哪种语言,都不建议在 eConnect() 后立刻发送大量业务请求。更稳的方式是:
- 连接 TWS / IB Gateway。
- 启动
EReader。 - 启动
processMsgs()循环。 - 等到
nextValidId()回调。 - 再开始请求行情、合约、账户或提交订单。
如果没有等 nextValidId(),程序可能在初始握手还没完成时就发送业务请求,表现为无回调、Not connected、订单 ID 异常或偶发失败。
线程设计建议
Section titled “线程设计建议”在 C++、C# 和 Java 中,建议把线程职责分清楚:
| 线程或任务 | 职责 |
|---|---|
| UI 线程 | 只负责界面显示,不直接阻塞 API 消息循环 |
| EReader 线程 | 从 socket 读取消息 |
| processMsgs 循环 | 解码消息并触发回调 |
| 业务工作线程 | 写数据库、策略计算、前端推送、批量任务 |
不要在 processMsgs() 所触发的回调里做长时间阻塞操作。回调里只做轻量处理,把重活交给业务队列。
| 现象 | 常见原因 |
|---|---|
| 连接成功但没有任何回调 | reader.processMsgs() 没有持续运行 |
| 只收到部分回调 | 回调里做了耗时任务,消息处理被堵住 |
| UI 卡死 | 把 API 消息循环放在 UI 线程里直接跑 |
| 断线后无法恢复 | 旧的 reader 线程和 signal 没有清理,重连逻辑重复创建对象 |
| 多客户端混乱 | 多个程序使用相同 clientId 或共享同一套状态 |