/*************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. This code is licensed under the Visual Studio SDK license terms. THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. ***************************************************************************/ namespace Microsoft.VisualStudio.Project { using System; using System.Diagnostics; using System.Globalization; using System.Threading; using System.Windows.Forms; internal sealed class UIThread : IDisposable { private WindowsFormsSynchronizationContext synchronizationContext; #if DEBUG /// /// Stack trace when synchronizationContext was captured /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] private StackTrace captureStackTrace; #endif private Thread uithread; /// /// 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(); internal 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; } } /// /// Gets a value indicating whether unit tests are running. /// internal static bool IsUnitTest { get; set; } #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")] internal void InitUnitTestingMode() { Debug.Assert(this.synchronizationContext == null, "Context has already been captured; too late to InitUnitTestingMode"); IsUnitTest = true; } [Conditional("DEBUG")] internal void MustBeCalledFromUIThread() { Debug.Assert(this.uithread == System.Threading.Thread.CurrentThread || IsUnitTest, "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")] internal void Run(Action a) { if (IsUnitTest) { 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")] internal void RunSync(Action a) { if (IsUnitTest) { 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; } } /// /// Initializes this object. /// private void Initialize() { if (IsUnitTest) 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); } } } }