/// <summary>Waits for the associated process to exit.</summary> /// <param name="millisecondsTimeout">The amount of time to wait, or -1 to wait indefinitely.</param> /// <returns>true if the process exited; false if the timeout occurred.</returns> internal bool WaitForExit(int millisecondsTimeout) { Debug.Assert(!Monitor.IsEntered(_gate)); if (_isChild) { lock (_gate) { // If we already know that the process exited, we're done. if (_exited) { return(true); } } ManualResetEvent exitEvent = EnsureExitedEvent(); return(exitEvent.WaitOne(millisecondsTimeout)); } else { // Track the time the we start waiting. long startTime = Stopwatch.GetTimestamp(); // Polling loop while (true) { bool createdTask = false; CancellationTokenSource?cts = null; Task waitTask; // We're in a polling loop... determine how much time remains int remainingTimeout = millisecondsTimeout == Timeout.Infinite ? Timeout.Infinite : (int)Math.Max(millisecondsTimeout - Stopwatch.GetElapsedTime(startTime).TotalMilliseconds, 0); lock (_gate) { // If we already know that the process exited, we're done. if (_exited) { return(true); } // If a timeout of 0 was supplied, then we simply need to poll // to see if the process has already exited. if (remainingTimeout == 0) { // If there's currently a wait-in-progress, then we know the other process // hasn't exited (barring races and the polling interval). if (_waitInProgress != null) { return(false); } // No one else is checking for the process' exit... so check. // We're currently holding the _gate lock, so we don't want to // allow CheckForNonChildExit to block indefinitely. CheckForNonChildExit(); return(_exited); } // The process has not yet exited (or at least we don't know it yet) // so we need to wait for it to exit, outside of the lock. // If there's already a wait in progress, we'll do so later // by waiting on that existing task. Otherwise, we'll spin up // such a task. if (_waitInProgress != null) { waitTask = _waitInProgress; } else { createdTask = true; CancellationToken token = remainingTimeout == Timeout.Infinite ? CancellationToken.None : (cts = new CancellationTokenSource(remainingTimeout)).Token; waitTask = WaitForExitAsync(token); } } // lock(_gate) if (createdTask) { // We created this task, and it'll get canceled automatically after our timeout. // This Wait should only wake up when either the process has exited or the timeout // has expired. Either way, we'll loop around again; if the process exited, that'll // be caught first thing in the loop where we check _exited, and if it didn't exit, // our remaining time will be zero, so we'll do a quick remaining check and bail. waitTask.Wait(); cts?.Dispose(); } else { // It's someone else's task. We'll wait for it to complete. This could complete // either because our remainingTimeout expired or because the task completed, // which could happen because the process exited or because whoever created // that task gave it a timeout. In any case, we'll loop around again, and the loop // will catch these cases, potentially issuing another wait to make up any // remaining time. waitTask.Wait(remainingTimeout); } } } }