/// <summary> /// Called when exception happens in a GUI routine /// </summary> private void OnGuiUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { bool userLevel; string message = ExceptionMessage.GetUserMessage(e.Exception, out userLevel); if (userLevel) { // TODO FIX NOW would really like to find the window with focus, and not always use the main window... MainWindow.Focus(); MainWindow.StatusBar.LogError(message); e.Handled = true; } else { var feedbackSent = AppLog.SendFeedback("Unhandled Exception in GUI\r\n" + e.Exception.ToString(), true); var dialog = new PerfView.Dialogs.UnhandledExceptionDialog(MainWindow, e.Exception, feedbackSent); var ret = dialog.ShowDialog(); // If it returns, it means that the user has opted to continue. e.Handled = true; } }
/// <summary> /// This starts long running work. It is called on the GUI thread. /// Only one piece of work can be running at a time (this is simply to keep the /// model the user sees simple). /// /// If finally_ is present, it will be run at EndWork time (But before the /// 'response' delegate passed to EndWork). Logically it is a finally clause associated /// with the work (but not the 'response') Unlike 'response' this action /// will occur under ALL conditions, including cancellation. it is used for other /// GUID indications that work is in progress. /// /// If 'finally_' is present it will be executed on the GUI thread. /// </summary> public void StartWork(string message, Action work, Action finally_ = null) { // We only call this from the GUI thread if (Dispatcher.Thread != Thread.CurrentThread) { throw new InvalidOperationException("Work can only be started from the UI thread."); } // Because we are only called on the GUI thread, there is no race. if (m_work != null) { // PerfViewLogger.Log.DebugMessage("Must cancel " + m_workMessage + " before starting " + message); LogError("Must first cancel: " + m_workMessage + " before starting " + message); return; } // PerfViewLogger.Log.DebugMessage("Starting Working " + message); m_abortStarted = false; m_abortDidInterrupt = false; m_endWorkStarted = false; m_endWorkCompleted = false; m_worker = null; m_finally = finally_; if (m_parentWindow == null) { m_parentWindow = Helpers.AncestorOfType <Window>(this); } if (m_parentWindow != null) { m_origCursor = m_parentWindow.Cursor; m_parentWindow.Cursor = System.Windows.Input.Cursors.Wait; } // Update GUI state m_workTimeSec = 0; m_startTime = DateTime.Now; m_timer.IsEnabled = true; m_CancelButton.IsEnabled = true; m_workMessage = message; m_ProgressText.Text = "Working"; Background = m_blinkColor; var completeMessage = "Started: " + message; Status = completeMessage; LogWriter.WriteLine(completeMessage); m_loggedStatus = false; // This part may take a little bit of time, so we pass it off to another // thread (to keep the UI responsive, and then when it is done call // back (this.BeginInvoke), to finish it off. var currentCulture = CultureInfo.CurrentCulture; var currentUICulture = CultureInfo.CurrentUICulture; var workSemaphore = new SemaphoreSlim(1); workSemaphore.Wait(); m_work = Task.Run(() => { // Wait for the m_work variable to actually get assigned workSemaphore.Wait(); var oldCulture = Tuple.Create(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture); try { Thread.CurrentThread.CurrentCulture = currentCulture; Thread.CurrentThread.CurrentUICulture = currentUICulture; try { try { m_worker = Thread.CurrentThread; // At this point we can be aborted. // If abort was called before m_worker was initialized we need to kill this thread ourselves. if (m_abortStarted) { throw new ThreadInterruptedException(); } work(); Debug.Assert(m_endWorkStarted, "User did not call EndWork before returning from work body."); } catch (Exception ex) { EndWork(delegate() { if (!(ex is ThreadInterruptedException)) { bool userLevel; var errorMessage = ExceptionMessage.GetUserMessage(ex, out userLevel); if (userLevel) { LogError(errorMessage); } else { Log(errorMessage); LogError("An exceptional condition occurred, see log for details."); } } }); } Debug.Assert(m_worker == null || m_abortStarted); // EndWork should have been called and nulled this out. // If we started an abort, then a thread-interrupt might happen at any time until the abort is completed. // Thus we should wait around until the abort completes. if (m_abortStarted) { while (!m_abortDidInterrupt) { Thread.Sleep(1); } } } catch (ThreadInterruptedException) { } // we 'expect' ThreadInterruptedException so don't let them leak out. // Cancellation completed, means that the thread is dead. We don't allow another work item on this StatusBar // The current thread is dead. Debug.Assert(m_endWorkStarted); if (m_abortStarted) { Log("Cancellation Complete on thread " + Thread.CurrentThread.ManagedThreadId + " : (Elapsed Time: " + Duration.TotalSeconds.ToString("f3") + " sec)"); } } finally { Thread.CurrentThread.CurrentCulture = oldCulture.Item1; Thread.CurrentThread.CurrentUICulture = oldCulture.Item2; } }); // Now that m_work is assigned, allow the operation to proceed workSemaphore.Release(); SignalPropertyChange(nameof(IsWorking)); SignalPropertyChange(nameof(IsNotWorking)); }