场景:
很多时候我们会用三方库或者系统自带的库来实现某些功能,比如websocket库,我们可能会根据自己的业务在上面增加一些润色,比如连接方法,发送方法,自动重连。再比如操作sqlite,对系统的库增加一些方法等。这些实际就是对某库对象的一个封装。比如下面的代码:
import { webSocket } from "@kit.NetworkKit";
import { hilog } from "@kit.PerformanceAnalysisKit";
enum Status{
DISCONNECT,
CONNECTED,
CONNECTTING
}
export class WebsocketClient {
url: string | undefined;
ws: webSocket.WebSocket;
status: Status = Status.DISCONNECT;
DOAMIN: number = 0xFF;
TAG = "WebsocketClient";
constructor(url: String) {
this.url = url as string;
this.ws = webSocket.createWebSocket();
this.registerStatus();
}
connect() {
hilog.info(this.DOAMIN, this.TAG, `connect 当前状态:${this.status}`);
if(this.status === Status.DISCONNECT) {
this.status = Status.CONNECTTING;
this.ws.connect(this.url, (err: BusinessError<void>, data: boolean) => {
if(data) {
hilog.info(this.DOAMIN, this.TAG, `${this.url} is connected`);
} else {
hilog.error(this.DOAMIN, this.TAG, `${this.url} is error: ${JSON.stringify(err)}}`)
this.status = Status.DISCONNECT;
}
});
} else {
hilog.info(this.DOAMIN, this.TAG, `正在连接中或已连接`);
}
}
registerStatus() {
hilog.info(this.DOAMIN, this.TAG, "registerStatus");
this.ws.on("open", (err: BusinessError<void>, value: Object) => {
hilog.info(this.DOAMIN, this.TAG, `${this.url} onOpen`);
this.status = Status.CONNECTED;
});
this.ws.on("message", (err: BusinessError<void>, value: string | ArrayBuffer) => {
hilog.info(this.DOAMIN, this.TAG, `${this.url} onMessage: ${value}`);
});
this.ws.on("error", (value: BusinessError<void>) => {
hilog.error(this.DOAMIN, this.TAG, `${this.url} onError: ${JSON.stringify(value)}`);
this.status = Status.DISCONNECT;
});
this.ws.on("close", (err: BusinessError<void>, value: webSocket.CloseResult) => {
hilog.info(this.DOAMIN, this.TAG, `${this.url} onClose: ${JSON.stringify(value)}`);
this.status = Status.DISCONNECT;
});
}
async send(msg: string) {
hilog.info(this.DOAMIN, this.TAG, `send 当前状态:${this.status} 消息:${msg}`);
if(this.status === Status.CONNECTED) {
let result = await this.ws.send(msg);
hilog.info(this.DOAMIN, this.TAG, `发送消息: ${result}`);
} else {
hilog.error(this.DOAMIN, this.TAG, `send 当前未处于已连接状态不能发送`);
}
}
close() {
hilog.info(this.DOAMIN, this.TAG, `close 当前状态:${this.status}}`);
if(this.status !== Status.DISCONNECT) {
this.ws.close();
this.status = Status.DISCONNECT;
} else {
hilog.error(this.DOAMIN, this.TAG, `close 当前已经关闭`);
}
}
}
由于这些对象,不限于websocket,肯定不能放在UI主线程去工作的,而且还要支持在任意位置下去调用,比如在后台服务里有个什么同步调用接口的定时器,界面登录按钮等等。这对于java,kotlin,go,C++语言的开发再正常不过了,这样场景都是需要开多线程使用的。下面是潜意识里的arkts用法:
@Concurrent
function connect(client: WebsocketClient) {
client.connect();
}
@Concurrent
function send(client: WebsocketClient) {
client.send("发数据");
}
let client = new WebsocketClient("ws://localhost:8080/ws");
let task = new taskpool.Task(connect, client);
taskpool.execute(task);
let task2 = new taskpool.Task(send, client);
taskpool.execute(task2);
上面的调用实测是不行的,因为WebsocketClient在不同的线程中不是一个对象了,这样就乱了。那你会说,把WebsocketClient类做成单例的可以不,即static方法。不行!我已经实际测试过了。那么我能用prototype来挂载然后再调用吗?比如:
let client = new WebsocketClient("ws://localhost:8080/ws");
(client as ESObject).__proto__ = WebsocketClient.prototype;
还是不行,不能共享对象!
看到官网的教程说,想要共享对象也可以,共享的数据必须用@Sendable标注的类或者函数,而且连互斥锁都可以用。但是针对我上面说的这样场景根本不行,为什么?因为系统的webSocket.WebSocket不是Sendable的,所以不能用!那咋办?不能保证同一个对象就意味着不能在任何位置用了,那就废了。最近一直在摸索着这个问题,找到了一个差不多的解决方案,但是可能会牺牲多线程的特性,但好处就是没有数据竞争了。思路就是用进程线程间通讯的emitter。我直接上代码把:
import { JSON, taskpool } from '@kit.ArkTS';
import { WebsocketClient } from './WebsocketClient';
import { emitter } from '@kit.BasicServicesKit';
@Concurrent
function initWebSocket(url: string) {
let client = new WebsocketClient(url);
let connect: emitter.InnerEvent = {
eventId: 1
};
let send: emitter.InnerEvent = {
eventId: 2
};
let close: emitter.InnerEvent = {
eventId: 3
};
emitter.on(connect, (data: emitter.EventData) => {
client.connect();
})
emitter.on(send, (data: emitter.EventData) => {
client.send(JSON.stringify(data.data));
})
emitter.on(close, (data: emitter.EventData) => {
client.close();
})
}
@Concurrent
function connectWS() {
let connect: emitter.InnerEvent = {
eventId: 1
};
emitter.emit(connect);
}
@Concurrent
function sendWS(msg: string) {
let send: emitter.InnerEvent = {
eventId: 2
};
let eventData: emitter.EventData = {
data: {
content: msg,
}
};
emitter.emit(send, eventData);
}
@Concurrent
function closeWS() {
let close: emitter.InnerEvent = {
eventId: 3
};
emitter.emit(close);
}
@Entry
@Component
struct Index {
@State url: string = "ws://192.168.41.201:21000/ws";
@State message: string = "aaaaa";
build() {
Column({space: 15}) {
TextInput({text: this.url}).width('30%')
Button("初始化")
.onClick(() => {
let task = new taskpool.Task(initWebSocket, this.url);
taskpool.execute(task);
})
.width('30%')
Button("连接")
.onClick(() => {
let task = new taskpool.Task(connectWS);
taskpool.execute(task);
})
.width('30%')
Row({space: 10}) {
TextInput({text: this.message}).width('25%')
Button("发消息")
.onClick(() => {
let task = new taskpool.Task(sendWS, this.message);
taskpool.execute(task);
})
.width('25%')
}
.justifyContent(FlexAlign.Center)
.width('50%')
Button("关闭")
.onClick(() => {
let task = new taskpool.Task(closeWS);
taskpool.execute(task);
})
.width('30%')
}
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
}
上面的代码大家应该都能看懂,但是我还是要说一些细节点。initWebSocket这个函数专门用于接收消息,其他的是发送方,这样发送方无论哪个线程都是OK的,initWebSocket函数的emitter.on始终在一个线程中接收,这样就能保证无论有多少个线程,接收方都是按照消息队列收取的,也不存在数据竞争问题了。还有一个关键点,ws的内部功能都在非UI线程中了。问题得到解决,不过我没有压力测试,不知道emitter对于背压会不会有什么问题,大家可以测下。

从log截图看,接收的线程始终在4971中。
根据华为技术给我的答复,arkts确实不支持这样的场景,要么放主线程,要么通过主线程与子线程的通讯来实现,这个emitter是我无意间想到的。。。不知道算不算旁门左道。但至少解决了我的问题。而且我也想不通微信,京东这些大厂的app都是用了什么办法,这样的使用场景我觉得是非常的常见的。
还有关注到仓颉语言,大概看了下,它支持常规的多线程,共享内存,锁 都可以用,或许以后慢慢的发展会过渡到这个语言上。

1225

被折叠的 条评论
为什么被折叠?



