示例#1
0
        internal static PSKeyInfo ReadKey()
        {
            // Reading a key is handled on a different thread.  During process shutdown,
            // PowerShell will wait in it's ConsoleCtrlHandler until the pipeline has completed.
            // If we're running, we're most likely blocked waiting for user input.
            // This is a problem for two reasons.  First, exiting takes a long time (5 seconds
            // on Win8) because PowerShell is waiting forever, but the OS will forcibly terminate
            // the console.  Also - if there are any event handlers for the engine event
            // PowerShell.Exiting, those handlers won't get a chance to run.
            //
            // By waiting for a key on a different thread, our pipeline execution thread
            // (the thread ReadLine is called from) avoid being blocked in code that can't
            // be unblocked and instead blocks on events we control.

            // First, set an event so the thread to read a key actually attempts to read a key.
            _singleton._readKeyWaitHandle.Set();

            int handleId;

            System.Management.Automation.PowerShell ps = null;

            try
            {
                while (true)
                {
                    // Next, wait for one of three things:
                    //   - a key is pressed
                    //   - the console is exiting
                    //   - 300ms timeout - to process events if we're idle
                    //   - ReadLine cancellation is requested externally
                    handleId = WaitHandle.WaitAny(_singleton._requestKeyWaitHandles, 300);
                    if (handleId != WaitHandle.WaitTimeout)
                    {
                        break;
                    }

                    if (_handleIdleOverride is not null)
                    {
                        _handleIdleOverride(_singleton._cancelReadCancellationToken);
                        continue;
                    }

                    // If we timed out, check for event subscribers (which is just
                    // a hint that there might be an event waiting to be processed.)
                    var eventSubscribers = _singleton._engineIntrinsics?.Events.Subscribers;
                    if (eventSubscribers?.Count > 0)
                    {
                        bool runPipelineForEventProcessing = false;
                        foreach (var sub in eventSubscribers)
                        {
                            if (sub.SourceIdentifier.Equals(PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase))
                            {
                                // If the buffer is not empty, let's not consider we are idle because the user is in the middle of typing something.
                                if (_singleton._buffer.Length > 0)
                                {
                                    continue;
                                }

                                // There is an OnIdle event subscriber and we are idle because we timed out and the buffer is empty.
                                // Normally PowerShell generates this event, but PowerShell assumes the engine is not idle because
                                // it called PSConsoleHostReadLine which isn't returning. So we generate the event instead.
                                runPipelineForEventProcessing = true;
                                _singleton._engineIntrinsics.Events.GenerateEvent(PSEngineEvent.OnIdle, null, null, null);

                                // Break out so we don't genreate more than one 'OnIdle' event for a timeout.
                                break;
                            }

                            runPipelineForEventProcessing = true;
                        }

                        // If there are any event subscribers, run a tiny useless PowerShell pipeline
                        // so that the events can be processed.
                        if (runPipelineForEventProcessing)
                        {
                            if (ps == null)
                            {
                                ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
                                ps.AddScript("0", useLocalScope: true);
                            }

                            // To detect output during possible event processing, see if the cursor moved
                            // and rerender if so.
                            var console = _singleton._console;
                            var y       = console.CursorTop;
                            ps.Invoke();
                            if (y != console.CursorTop)
                            {
                                _singleton._initialY = console.CursorTop;
                                _singleton.Render();
                            }
                        }
                    }
                }
            }
            finally
            {
                ps?.Dispose();
            }

            if (handleId == ConsoleExiting)
            {
                // The console is exiting - throw an exception to unwind the stack to the point
                // where we can return from ReadLine.
                if (_singleton.Options.HistorySaveStyle == HistorySaveStyle.SaveAtExit)
                {
                    _singleton.SaveHistoryAtExit();
                }
                _singleton._historyFileMutex.Dispose();

                throw new OperationCanceledException();
            }

            if (handleId == CancellationRequested)
            {
                // ReadLine was cancelled. Save the current line to be restored next time ReadLine
                // is called, clear the buffer and throw an exception so we can return an empty string.
                _singleton.SaveCurrentLine();
                _singleton._getNextHistoryIndex = _singleton._history.Count;
                _singleton._current             = 0;
                _singleton._buffer.Clear();
                _singleton.Render();
                throw new OperationCanceledException();
            }

            var key = _singleton._queuedKeys.Dequeue();

            return(key);
        }