private void ParserThreadCallback() { while (!_tokenSource.Token.IsCancellationRequested) { //Check if the MRE has been set if (!_mre.WaitOne(100)) { continue; } if (!_queue.TryDequeue(out QueueData data)) { _mre.Reset(); continue; } //The vertical bar is a pipe character. inputA | inputB = run command B with the output of command A string[] inputs = data.Input.Split('|'); if (inputs.Length > 1) { //If we have a piped command, send it off to a new thread to be handled, //As each command needs to be handled in order Thread pipeThread = new Thread(() => PipeThreadCallback(inputs, data.Callback, data.Context)); pipeThread.Start(); _mre.Set(); continue; } string input = data.Input; //We don't lower the data because case sensitivity is an option for command matching CommandMetadata metadata; lock (_lock) { //Lock the metadata collection, and grab the first metadata that has a matching executor List <CommandMetadata> metadatas = _metadata.Where(m => m.GetFirstOrDefaultExecutorData(input) != null).ToList(); metadata = metadatas.FirstOrDefault(); } if (metadata == null) { data.Callback?.Invoke(InputResult.Unhandled, null); //No command matches, so ignore this input _mre.Set(); continue; } CommandExecutorData exeData = metadata.GetFirstOrDefaultExecutorData(input); RegexString trigger = exeData.ExecutorAttribute.CommandMatcher; input = trigger.RemoveMatchedString(input).TrimStart(); AbstractParser parser = _registry.GetParser(_registry, input, null, metadata, exeData, data.Context, data.Callback); try { parser.Start(); } finally { //Our job is done, so prepare for the next input _mre.Set(); } } _mre.Dispose(); }