Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VS Code 高性能的秘密 — Dispose Pattern #282

Open
AIluffy opened this issue Feb 12, 2019 · 1 comment
Open

VS Code 高性能的秘密 — Dispose Pattern #282

AIluffy opened this issue Feb 12, 2019 · 1 comment

Comments

@AIluffy
Copy link

AIluffy commented Feb 12, 2019

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

@shaozj
Copy link

shaozj commented Mar 4, 2022

好多图挂了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants