未验证 提交 e297e748 编写于 作者: A alpalla 提交者: GitHub

Allow N instances of the same task (#89872)

Feature request implementation to allow multiple instances of the same task.
Fixes #32264
Co-authored-by: NAlex Ross <alros@microsoft.com>
上级 24de3c0f
......@@ -1277,14 +1277,15 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
if (active && active.same) {
if (this._taskSystem?.isTaskVisible(executeResult.task)) {
const message = nls.localize('TaskSystem.activeSame.noBackground', 'The task \'{0}\' is already active.', executeResult.task.getQualifiedLabel());
let lastInstance = this.getTaskSystem().getLastInstance(executeResult.task) ?? executeResult.task;
this.notificationService.prompt(Severity.Info, message,
[{
label: nls.localize('terminateTask', "Terminate Task"),
run: () => this.terminate(executeResult.task)
run: () => this.terminate(lastInstance)
},
{
label: nls.localize('restartTask', "Restart Task"),
run: () => this.restart(executeResult.task)
run: () => this.restart(lastInstance)
}],
{ sticky: true }
);
......@@ -1967,11 +1968,21 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
private createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry): TaskQuickPickEntry[] {
let count: { [key: string]: number; } = {};
if (tasks === undefined || tasks === null || tasks.length === 0) {
return [];
}
const TaskQuickPickEntry = (task: Task): TaskQuickPickEntry => {
return { label: task._label, description: this.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined };
let entryLabel = task._label;
let commonKey = task._id.split('|')[0];
if (count[commonKey]) {
entryLabel = entryLabel + ' (' + count[commonKey].toString() + ')';
count[commonKey]++;
} else {
count[commonKey] = 1;
}
return { label: entryLabel, description: this.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined };
};
function fillEntries(entries: QuickPickInput<TaskQuickPickEntry>[], tasks: Task[], groupLabel: string): void {
if (tasks.length) {
......@@ -2034,6 +2045,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
entries = tasks.map<TaskQuickPickEntry>(task => TaskQuickPickEntry(task));
}
count = {};
return entries;
}
......
......@@ -59,6 +59,25 @@ interface ActiveTerminalData {
promise: Promise<ITaskSummary>;
}
class InstanceManager {
private _currentInstances: number = 0;
private _counter: number = 0;
addInstance() {
this._currentInstances++;
this._counter++;
}
removeInstance() {
this._currentInstances--;
}
get instances() {
return this._currentInstances;
}
get counter() {
return this._counter;
}
}
class VariableResolver {
constructor(public workspaceFolder: IWorkspaceFolder | undefined, public taskSystemInfo: TaskSystemInfo | undefined, private _values: Map<string, string>, private _service: IConfigurationResolverService | undefined) {
......@@ -153,6 +172,7 @@ export class TerminalTaskSystem implements ITaskSystem {
};
private activeTasks: IStringDictionary<ActiveTerminalData>;
private instances: IStringDictionary<InstanceManager>;
private busyTasks: IStringDictionary<Task>;
private terminals: IStringDictionary<TerminalData>;
private idleTaskTerminals: LinkedMap<string, string>;
......@@ -185,6 +205,7 @@ export class TerminalTaskSystem implements ITaskSystem {
) {
this.activeTasks = Object.create(null);
this.instances = Object.create(null);
this.busyTasks = Object.create(null);
this.terminals = Object.create(null);
this.idleTaskTerminals = new LinkedMap<string, string>();
......@@ -207,18 +228,32 @@ export class TerminalTaskSystem implements ITaskSystem {
}
public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult {
let commonKey = task._id.split('|')[0];
let validInstance = task.runOptions && task.runOptions.instanceLimit && this.instances[commonKey] && this.instances[commonKey].instances < task.runOptions.instanceLimit;
let instance = this.instances[commonKey] ? this.instances[commonKey].instances : 0;
this.currentTask = new VerifiedTask(task, resolver, trigger);
let terminalData = this.activeTasks[task.getMapKey()];
if (terminalData && terminalData.promise) {
let taskClone = undefined;
if (instance > 0) {
taskClone = task.clone();
taskClone._id += '|' + this.instances[commonKey].counter.toString();
}
let taskToExecute = taskClone ?? task;
let lastTaskInstance = this.getLastInstance(task);
let terminalData = lastTaskInstance ? this.activeTasks[lastTaskInstance.getMapKey()] : undefined;
if (terminalData && terminalData.promise && !validInstance) {
this.lastTask = this.currentTask;
return { kind: TaskExecuteKind.Active, task, active: { same: true, background: task.configurationProperties.isBackground! }, promise: terminalData.promise };
return { kind: TaskExecuteKind.Active, task: terminalData.task, active: { same: true, background: task.configurationProperties.isBackground! }, promise: terminalData.promise };
}
try {
const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this.executeTask(task, resolver, trigger) };
const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this.executeTask(taskToExecute, resolver, trigger) };
executeResult.promise.then(summary => {
this.lastTask = this.currentTask;
});
if (!this.instances[commonKey]) {
this.instances[commonKey] = new InstanceManager();
}
this.instances[commonKey].addInstance();
return executeResult;
} catch (error) {
if (error instanceof TaskError) {
......@@ -304,6 +339,17 @@ export class TerminalTaskSystem implements ITaskSystem {
return Object.keys(this.activeTasks).map(key => this.activeTasks[key].task);
}
public getLastInstance(task: Task): Task | undefined {
let lastInstance = undefined;
let commonId = task._id.split('|')[0];
Object.keys(this.activeTasks).forEach((key) => {
if (commonId === this.activeTasks[key].task._id.split('|')[0]) {
lastInstance = this.activeTasks[key].task;
}
});
return lastInstance;
}
public getBusyTasks(): Task[] {
return Object.keys(this.busyTasks).map(key => this.busyTasks[key]);
}
......@@ -320,6 +366,20 @@ export class TerminalTaskSystem implements ITaskSystem {
});
}
private removeFromActiveTasks(task: Task): void {
if (!this.activeTasks[task.getMapKey()]) {
return;
}
delete this.activeTasks[task.getMapKey()];
let commonKey = task._id.split('|')[0];
if (this.instances[commonKey]) {
this.instances[commonKey].removeInstance();
if (this.instances[commonKey].instances === 0) {
delete this.instances[commonKey];
}
}
}
public terminate(task: Task): Promise<TaskTerminateResponse> {
let activeTerminal = this.activeTasks[task.getMapKey()];
if (!activeTerminal) {
......@@ -675,7 +735,7 @@ export class TerminalTaskSystem implements ITaskSystem {
if (this.busyTasks[mapKey]) {
delete this.busyTasks[mapKey];
}
delete this.activeTasks[key];
this.removeFromActiveTasks(task);
this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed));
if (exitCode !== undefined) {
// Only keep a reference to the terminal if it is not being disposed.
......@@ -753,7 +813,7 @@ export class TerminalTaskSystem implements ITaskSystem {
onData.dispose();
onExit.dispose();
let key = task.getMapKey();
delete this.activeTasks[key];
this.removeFromActiveTasks(task);
this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed));
if (exitCode !== undefined) {
// Only keep a reference to the terminal if it is not being disposed.
......@@ -1104,7 +1164,7 @@ export class TerminalTaskSystem implements ITaskSystem {
// For correct terminal re-use, the task needs to be deleted immediately.
// Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate.
const mapKey = task.getMapKey();
delete this.activeTasks[mapKey];
this.removeFromActiveTasks(task);
if (this.busyTasks[mapKey]) {
delete this.busyTasks[mapKey];
}
......
......@@ -329,6 +329,11 @@ const runOptions: IJSONSchema = {
description: nls.localize('JsonSchema.tasks.runOn', 'Configures when the task should be run. If set to folderOpen, then the task will be run automatically when the folder is opened.'),
default: 'default'
},
instanceLimit: {
type: 'number',
description: nls.localize('JsonSchema.tasks.instanceLimit', 'The number of instances of the task that are allowed to run simultaneously.'),
default: 1
},
},
description: nls.localize('JsonSchema.tasks.runOptions', 'The task\'s run related options')
};
......
......@@ -133,6 +133,7 @@ export interface PresentationOptionsConfig {
export interface RunOptionsConfig {
reevaluateOnRerun?: boolean;
runOn?: string;
instanceLimit?: number;
}
export interface TaskIdentifier {
......@@ -681,7 +682,8 @@ export namespace RunOptions {
export function fromConfiguration(value: RunOptionsConfig | undefined): Tasks.RunOptions {
return {
reevaluateOnRerun: value ? value.reevaluateOnRerun : true,
runOn: value ? RunOnOptions.fromString(value.runOn) : Tasks.RunOnOptions.default
runOn: value ? RunOnOptions.fromString(value.runOn) : Tasks.RunOnOptions.default,
instanceLimit: value ? value.instanceLimit : 1
};
}
}
......
......@@ -133,6 +133,7 @@ export interface ITaskSystem {
isActive(): Promise<boolean>;
isActiveSync(): boolean;
getActiveTasks(): Task[];
getLastInstance(task: Task): Task | undefined;
getBusyTasks(): Task[];
canAutoTerminate(): boolean;
terminate(task: Task): Promise<TaskTerminateResponse>;
......
......@@ -523,10 +523,11 @@ export enum RunOnOptions {
export interface RunOptions {
reevaluateOnRerun?: boolean;
runOn?: RunOnOptions;
instanceLimit?: number;
}
export namespace RunOptions {
export const defaults: RunOptions = { reevaluateOnRerun: true, runOn: RunOnOptions.default };
export const defaults: RunOptions = { reevaluateOnRerun: true, runOn: RunOnOptions.default, instanceLimit: 1 };
}
export abstract class CommonTask {
......
......@@ -94,6 +94,14 @@ export class ProcessTaskSystem implements ITaskSystem {
return result;
}
public getLastInstance(task: Task): Task | undefined {
let result = undefined;
if (this.activeTask) {
result = this.activeTask;
}
return result;
}
public getBusyTasks(): Task[] {
return [];
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册