kaola-fed / blog

kaola blog

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

VS Code 高性能的秘密 — Dispose Pattern

AIluffy opened this issue · comments

VS Code是微软出品的新一代轻量级编辑器,一出道便以简洁大气的界面、卓越的性能、以及灵活的可扩展性吸引了大批的拥趸。

VS Code Icon

插件化是VS Code的精髓,大部分功能比如command、key binding、context menu都是通过它对外提供的一套扩展API实现并集成到Code中。VS Code使用多进程的架构来分别处理编辑器的渲染与执行,每开一个窗口,便会为该窗口创建一个进程执行插件,该进程即为Extension Host。Extension Host按需激活插件,同一时间内,插件代码可能被运行多次。

VS Code Architectur

为了保证插件的高效运行,VS Code使用了Dispose模式,大部分插件API都实现了IDisposable接口,生成的对象则会拥有一个dispose函数属性。

interface IDisposable {
	dispose(): void;
}

Dispose模式主要用来资源管理,资源比如内存被对象占用,则会通过调用方法来释放,这些方法通常被命名为‘close’,‘dispose’,‘free’,‘release’。一个著名的例子便是C#,C#通过Dipose Pattern来释放不受CLR(Common Language Runtime)管理的非托管资源。

VS Code是由Javascript实现的,众所周知,Javascript的内存分配是通过GC(garbage collector)进行管理,大部分情况下它都是自动执行且对用户不可见的。然而这种自动化的管理方式却存在一个潜在的问题,就是Javascript开发者会错误的认为他们不需要再关心内存管理了,从而再无意间书写一些不利于内存回收的代码。

所以,最清楚被分配的内存在未来是否需要使用的还是开发者,但是每次使用完一个对象后就手动的将其销毁,这样的做法即不高效,也不可靠。正因为此,VS Code使用了Dispose Pattern来管理对象销毁。当扩展功能执行时,Extension Host会在正确的时机调用dispose方法,销毁Code生成的对象,减少内存使用。比如说,方法‘setStatusBarMessage(value: string)’返回一个‘Disposable’对象,当调用dispose方法的时候会移除掉信息对象。

Dispose Pattern

Dispose pattern的实现如下

// 第一个重载参数是单个disposable类型
function dispose<T extends IDisposable>(disposable: T): T;
// 第二个重载参数是多个disposable类型传参数,参数可能为undefined。
function dispose<T extends IDisposable>(...disposables: Array<T | undefined>): T[];
// 第三个重载参数是一个disposable类型的数组。
function dispose<T extends IDisposable>(disposables: T[]): T[];
// 第三个重载参数为两种,第一个是disposable类型或disposable数组类型,剩余的为disposable类型。
function dispose<T extends IDisposable>(first: T | T[], ...rest: T[]): T | T[] | undefined {
  // 如果第一个参数是数组,则依次调用传参数的dispose方法
  if (Array.isArray(first)) {
  	first.forEach(d => d && d.dispose());
  	// 返回空的数组
  	return [];
  } else if (rest.length === 0) {
   // 如果没有没有剩余参数
  	if (first) {
  	   // 如果存在first
  	   // 调用第一个dispose
  		first.dispose();
  	  // 返回first
  		return first;
  	}
  	
  	return undefined;
  } else {
    // first不是数组,且rest长度不为0
  	dispose(first);
  	dispose(rest);
  	
  	// 返回空数组
  	return [];
  }
}

// implement IDisposable 的Disposable 抽象类
abstract class Disposable implements IDisposable {
  
  // Disposable类的静态对象,用于返回一个包含空的dispose方法的IDisposable对象。dispose被执行了,则表示该对象不再需要了。
  // 部分基础API使用了该对象,用于标志资源释放。
	static None = Object.freeze<IDisposable>({ dispose() { } });
  
  // protected属性toDispose返回protected对象_toDispose, 该对象初始值是一个空的数组。
	protected _toDispose: IDisposable[] = [];
	// 返回IDisposable数组。
	protected get toDispose(): IDisposable[] { return this._toDispose; }
  
  // 设置状态标志,表示该对象是否有被销毁。
	private _lifecycle_disposable_isDisposed = false;
  
  // 暴露公共方法dispose,执行完后将_lifecycle_disposable_isDisposed状态标志设为true,同时调用lifecycle内的dispose方法处理_toDispose数组,并重新赋值空数组。
	public dispose(): void {
		this._lifecycle_disposable_isDisposed = true;
		this._toDispose = dispose(this._toDispose);
	}
  
  // 内部方法注册实例,若_lifecycle_disposable_isDisposed为true,则表明该方法已经被dispose过,则不能再使用,需dispose掉,否则,推入_toDispose数组。
	protected _register<T extends IDisposable>(t: T): T {
	  // 判断这个对象有没有被dispose过
		if (this._lifecycle_disposable_isDisposed) {
			console.warn('Registering disposable on object that has already been disposed.');
			t.dispose();
		} else {
			this._toDispose.push(t);
		}
		
		return t;
	}
}

扩展API大部分功能类或功能方法都通过上面的抽象类Disposable或接口IDisposable实现dispose方法。下面的函数示例了一个功能类DelayedDragHandler如何实现dispose方法,当HTMLElement的延迟拖动方法执行完后,其实例对象的timeout对象会被及时清除,避免内存占用。

dispose object

/**
 * A helper that will execute a provided function when the provided HTMLElement receives
 *  dragover event for 800ms. If the drag is aborted before, the callback will not be triggered.
 */
export class DelayedDragHandler extends Disposable {
	private timeout: any;

	constructor(container: HTMLElement, callback: () => void) {
		super();

		this._register(addDisposableListener(container, 'dragover', () => {
			if (!this.timeout) {
				this.timeout = setTimeout(() => {
					callback();

					this.timeout = null;
				}, 800);
			}
		}));
	}

	private clearDragTimeout(): void {
		if (this.timeout) {
			clearTimeout(this.timeout);
			this.timeout = null;
		}
	}

	dispose(): void {
		super.dispose();

		this.clearDragTimeout();
	}
}

引用:

by zhangxueai@corp.netease.com

好多图挂了