vscode中的基本概念(二)

19 阅读5分钟

通信机制

  • Electron框架是一个典型的多进程多线程的框架,免不了需要去设计进程间的通信。

  • 接下来介绍一下VSCode中的一些关于通信方面的概念

  • 通信机制会有如下内容需要设计:

    • 协议设计-Protocol
    • 通信频道-Channel
    • 连接-Connection
    • 服务端-IPCServer
    • 客户端-IPCClient

通信概念释义

概念名词概念解释作用
Protocol通信协议通信的基础协议规范
Channel客户端频道端与端之间进行信息传输的通道,类似于电台频道
ServerChannel服务端频道端与端之间进行信息传输的通道,类似于电台频道
ChannelClient频道的客户端客户端频道的管理
ChannelServer频道的服务端服务端频道的管理
Connection连接端与端之间的连接对应关系
IPCClientIPC 客户端负责连接的建立以及 Channel 的注册和获取
IPCServerIPC 服务端负责连接的建立以及 Channel 的注册和获取
  • 协议(Protocol)

    • 两个端之间进行消息通信的约定,比如我们是通过语言还是手语比划进行通信,需要通过协议进行约定。

    • 在 VS Code 中,约定了最基础的协议范围包括发送和接收消息两个方法:

      • 发送:send
      • 接收:onMessage
  • 频道(Channel)

    • 狭义定义上,频道又叫信道,信道是信号在通信系统重传输的通道,是信号从发射端传输到接收端所经过的传输煤质。

    • 在 VS Code 中,频道是一组可供其他端进行调用的服务集合。一个标准的频道有两个功能:

      • 点播:call
      • 收听:listen
    • 在频道中,Server是专门处理消息的类,Client是专门发送消息的类。

  • 客户端&服务端(Server&Client)

    • 客户端 & 服务端是频道的承载主体。一般客户端是指发起连接的一端,服务端是被连接的一端。
    • 在 VS Code 中,服务端提供一系列服务的频道;渲染进程是客户端,调用服务端频道中的服务或者收听服务端消息。不管是服务端还是客户端,都需要具备发送和接受消息的能力,才能实现正常的通信。
  • 连接(Connection)

    • 客户端 & 服务端之间进行通信依赖的连接。
    • 在 VS Code 中一个连接其实是一对客户端与服务端的对应关系。
  • 上述接口和类的定义对应文件为:src/vs/base/parts/ipc/common/ipc.ts

实现示例

  • 请求实现(基于 IMessagePassingProtocol 协议)
class QueueProtocol implements IMessagePassingProtocol {
  private buffering = true;
  private buffers: VSBuffer[] = [];

  private readonly _onMessage = new Emitter<VSBuffer>({
    onDidAddFirstListener: () => {
      for (const buffer of this.buffers) {
        this._onMessage.fire(buffer);
      }

      this.buffers = [];
      this.buffering = false;
    },
    onDidRemoveLastListener: () => {
      this.buffering = true;
    }
  });

  readonly onMessage = this._onMessage.event;
  other!: QueueProtocol;

  send(buffer: VSBuffer): void {
    this.other.receive(buffer);
  }

  protected receive(buffer: VSBuffer): void {
    if (this.buffering) {
      this.buffers.push(buffer);
    } else {
      this._onMessage.fire(buffer);
    }
  }
}
  • 客户端(基于IPCClient)
class TestIPCClient extends IPCClient<string> {
  private readonly _onDidDisconnect = new Emitter<void>();
	readonly onDidDisconnect = this._onDidDisconnect.event;

	constructor(protocol: IMessagePassingProtocol, id: string) {
		super(protocol, id);
	}

	override dispose(): void {
		this._onDidDisconnect.fire();
		super.dispose();
	}
}
  • 服务端(基于IPCServer)
class TestIPCServer extends IPCServer<string> {
	private readonly onDidClientConnect: Emitter<ClientConnectionEvent>;

	constructor() {
		const onDidClientConnect = new Emitter<ClientConnectionEvent>();
		super(onDidClientConnect.event);
		this.onDidClientConnect = onDidClientConnect;
	}

  // 创建一个客户端 & 服务端的连接
	createConnection(id: string): IPCClient<string> {
		const [pc, ps] = createProtocolPair();
    const pc = new QueueProtocol();
    const ps = new QueueProtocol();
    pc.other = ps;
    ps.other = pc;
		const client = new TestIPCClient(pc, id);

		this.onDidClientConnect.fire({
			protocol: ps,
			onDidClientDisconnect: client.onDidDisconnect
		});

		return client;
	}
}
  • 服务端频道及其对应的服务
// 服务接口
interface ITestService {
	marco(): Promise<string>;
	onPong: Event<string>;
}

// 服务
class TestService implements ITestService {
	private readonly _onPong = new Emitter<string>();
	readonly onPong = this._onPong.event;

  marco(): Promise<string> {
		return Promise.resolve('polo');
	}

	ping(msg: string): void {
		this._onPong.fire(msg);
	}
}

// 服务频道
class TestChannel implements IServerChannel {
	constructor(private service: ITestService) { }

	call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
		switch (command) {
			case 'marco': return this.service.marco();
			default: return Promise.reject(new Error('not implemented'));
		}
	}

	listen(_: unknown, event: string, arg?: any): Event<any> {
		switch (event) {
			case 'onPong': return this.service.onPong;
			default: throw new Error('not implemented');
		}
	}
}
  • 客户端频道服务
class TestChannelClient implements ITestService {
	get onPong(): Event<string> {
		return this.channel.listen('onPong');
	}

	constructor(private channel: IChannel) { }

	marco(): Promise<string> {
		return this.channel.call('marco');
	}
}
  • 实现一对多IPC通信
// 创建服务
const service = new TestService();
// 创建服务端
const server = new TestIPCServer();
// 创建服务端频道
const channel = new TestChannel(service);
// 服务端注册服务
server.registerChannel('channel', channel);

// 创建客户端-1
const client1 = server.createConnection('client1');
// 创建客户端-1对应的频道服务
const ipcService1 = new TestChannelClient(client1.getChannel('channel'));
// 创建客户端-2
const client2 = server.createConnection('client2');
// 创建客户端-2对应的频道服务
const ipcService2 = new TestChannelClient(client2.getChannel('channel'));
  • 客户端监听服务端
ipcService1.onPong(() => console.log('Receive ping message on service1'));
ipcService2.onPong(() => console.log('Receive ping message on service2'));
  • 服务端通知:
service.ping('hello world');

资料总结

同步屏障

  • barrier 类通常指的是 TypeScript 或 JavaScript 中的 Barrier 类型,它用于控制异步操作的执行顺序。Barrier 类型是 vscode 扩展 API 中的一部分,用于确保异步操作在特定的顺序下执行,或者在所有操作都完成之后才执行某些操作。

  • Barrier 类型的主要作用包括:

    • 同步异步操作:Barrier 可以用来同步多个异步操作,确保它们按照特定的顺序执行。这在需要等待多个异步操作完成后才能继续执行下一步的场景中非常有用。
    • 等待所有操作完成:使用 Barrier,你可以等待一组异步操作全部完成后再执行某些代码。这可以通过 Barrier.wait() 方法实现,该方法会阻塞直到所有注册的异步操作都完成。
    • 避免竞态条件:在多线程或多任务环境中,Barrier 可以帮助避免竞态条件,确保资源在被访问之前已经准备好。
    • 简化异步代码:通过使用 Barrier,可以简化异步代码的复杂性,使得代码更加清晰和易于管理。
  • 在 VSCode 扩展开发中,Barrier 类型通常用于以下场景:

    • 当扩展需要等待用户完成某些操作(如选择文件、输入文本等)后再继续执行。
    • 当扩展需要在多个异步操作完成后更新 UI 或执行其他逻辑。
export class Barrier {
	private _isOpen: boolean;
	private _promise: Promise<boolean>;
	private _completePromise!: (v: boolean) => void;

	constructor() {
		this._isOpen = false;
		this._promise = new Promise<boolean>((c, e) => {
			this._completePromise = c;
		});
	}

	isOpen(): boolean {
		return this._isOpen;
	}

	open(): void {
		this._isOpen = true;
		this._completePromise(true);
	}

	wait(): Promise<boolean> {
		return this._promise;
	}
}