/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ namespace Microsoft.VisualStudio.Project { using System; using System.Diagnostics; using System.Globalization; using System.Threading; using System.Windows.Forms; public sealed class UIThread : IDisposable { private WindowsFormsSynchronizationContext synchronizationContext; private bool isUnitTestingMode; private Thread uithread; #if DEBUG /// /// Stack trace when synchronizationContext was captured /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] private StackTrace captureStackTrace; #endif /// /// RunSync puts orignal exception stacktrace to Exception.Data by this key if action throws on UI thread /// /// WrappedStacktraceKey is a string to keep exception serializable. private const string WrappedStacktraceKey = "$$Microsoft.VisualStudio.Package.UIThread.WrappedStacktraceKey$$"; /// /// The singleton instance. /// private static volatile UIThread instance = new UIThread(); public UIThread() { this.Initialize(); } /// /// Gets the singleton instance /// public static UIThread Instance { get { return instance; } } /// /// Checks whether this is the UI thread. /// public bool IsUIThread { get { return this.uithread == System.Threading.Thread.CurrentThread; } } #region IDisposable Members /// /// Dispose implementation. /// public void Dispose() { if (this.synchronizationContext != null) { this.synchronizationContext.Dispose(); } } #endregion /// /// Initializes unit testing mode for this object /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] public void InitUnitTestingMode() { Debug.Assert(this.synchronizationContext == null, "Context has already been captured; too late to InitUnitTestingMode"); this.isUnitTestingMode = true; } [Conditional("DEBUG")] public void MustBeCalledFromUIThread() { Debug.Assert(this.uithread == System.Threading.Thread.CurrentThread || this.isUnitTestingMode, "This must be called from the GUI thread"); } /// /// Runs an action asynchronously on an associated forms synchronization context. /// /// The action to run [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] public void Run(Action a) { if (this.isUnitTestingMode) { a(); return; } Debug.Assert(this.synchronizationContext != null, "The SynchronizationContext must be captured before calling this method"); #if DEBUG StackTrace stackTrace = new StackTrace(true); #endif this.synchronizationContext.Post(delegate(object ignore) { try { this.MustBeCalledFromUIThread(); a(); } #if DEBUG catch (Exception e) { // swallow, random exceptions should not kill process Debug.Assert(false, string.Format(CultureInfo.InvariantCulture, "UIThread.Run caught and swallowed exception: {0}\n\noriginally invoked from stack:\n{1}", e.ToString(), stackTrace.ToString())); } #else catch (Exception) { // swallow, random exceptions should not kill process } #endif }, null); } /// /// Runs an action synchronously on an associated forms synchronization context /// /// The action to run. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] public void RunSync(Action a) { // if we're already on the UI thread run immediately - this prevents // re-entrancy at unexpected times when we're already on the UI thread. if (this.isUnitTestingMode || uithread == Thread.CurrentThread) { a(); return; } Exception exn = null; ; Debug.Assert(this.synchronizationContext != null, "The SynchronizationContext must be captured before calling this method"); // Send on UI thread will execute immediately. this.synchronizationContext.Send(ignore => { try { this.MustBeCalledFromUIThread(); a(); } catch (Exception e) { exn = e; } }, null ); if (exn != null) { // throw exception on calling thread, preserve stacktrace if (!exn.Data.Contains(WrappedStacktraceKey)) exn.Data[WrappedStacktraceKey] = exn.StackTrace; throw exn; } } /// /// Runs an action synchronously on an associated forms synchronization context /// /// Return type /// The function /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] public T RunSync(Func a) { T retValue = default(T); if (this.isUnitTestingMode) { return a(); } Exception exn = null; ; Debug.Assert(this.synchronizationContext != null, "The SynchronizationContext must be captured before calling this method"); // Send on UI thread will execute immediately. this.synchronizationContext.Send(ignore => { try { this.MustBeCalledFromUIThread(); retValue = a(); } catch (Exception e) { exn = e; } }, null ); if (exn != null) { // throw exception on calling thread, preserve stacktrace if (exn.Data != null && !exn.Data.Contains(WrappedStacktraceKey)) exn.Data[WrappedStacktraceKey] = exn.StackTrace; throw exn; } return retValue; } /// /// Initializes this object. /// private void Initialize() { if (this.isUnitTestingMode) return; this.uithread = System.Threading.Thread.CurrentThread; if (this.synchronizationContext == null) { #if DEBUG // This is a handy place to do this, since the product and all interesting unit tests // must go through this code path. AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(delegate(object sender, UnhandledExceptionEventArgs args) { if (args.IsTerminating) { string s = String.Format(CultureInfo.InvariantCulture, "An unhandled exception is about to terminate the process. Exception info:\n{0}", args.ExceptionObject.ToString()); Debug.Assert(false, s); } }); this.captureStackTrace = new StackTrace(true); #endif this.synchronizationContext = new WindowsFormsSynchronizationContext(); } else { // Make sure we are always capturing the same thread. Debug.Assert(this.uithread == Thread.CurrentThread); } } } }