diff --git a/vsintegration/src/FSharp.ProjectSystem.Base/Project/UIThread.cs b/vsintegration/src/FSharp.ProjectSystem.Base/Project/UIThread.cs new file mode 100644 index 00000000000..1a0bb06e4ec --- /dev/null +++ b/vsintegration/src/FSharp.ProjectSystem.Base/Project/UIThread.cs @@ -0,0 +1,232 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; +using System.Xml; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.IO; +using System.Text; +using System.Drawing; +using System.Windows.Forms; +using System.Diagnostics; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.Shell; +using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; +using IServiceProvider = System.IServiceProvider; +using VsShell = Microsoft.VisualStudio.Shell.VsShellUtilities; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.VisualStudio.FSharp.LanguageService +{ + internal static class UIThread + { + static SynchronizationContext ctxt; + static bool isUnitTestingMode = false; +#if DEBUG + static StackTrace captureStackTrace; // stack trace when ctxt was captured + static Thread uithread; +#endif + public static SynchronizationContext TheSynchronizationContext + { + get + { + Debug.Assert(ctxt != null, "Tried to get TheSynchronizationContext before it was captured"); + return ctxt; + } + } + + public static void InitUnitTestingMode() + { + Debug.Assert(ctxt == null, "Context has already been captured; too late to InitUnitTestingMode"); + isUnitTestingMode = true; + } + + [Conditional("DEBUG")] + public static void MustBeCalledFromUIThread() + { +#if DEBUG + Debug.Assert(uithread == System.Threading.Thread.CurrentThread || isUnitTestingMode, "This must be called from the GUI thread"); +#endif + } + public static void CaptureSynchronizationContext() + { + if (isUnitTestingMode) return; +#if DEBUG + uithread = System.Threading.Thread.CurrentThread; +#endif + + if (ctxt == 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("An unhandled exception is about to terminate the process. Exception info:\n{0}", args.ExceptionObject.ToString()); + Debug.Assert(false, s); + } + }); + captureStackTrace = new StackTrace(true); +#endif + ctxt = new WindowsFormsSynchronizationContext(); + } + else + { +#if DEBUG + // Make sure we are always capturing the same thread. + Debug.Assert(uithread == Thread.CurrentThread); +#endif + } + } + private static readonly Queue ourUIQueue = new Queue(); + private static bool ourIsReentrancy; + + // Runs the action on UI thread. Prevents from reentracy. + public static void Run(Action action) + { + if (isUnitTestingMode) + { + action(); + return; + } + Debug.Assert(ctxt != null, "The SynchronizationContext must be captured before calling this method"); +#if DEBUG + StackTrace stackTrace = new StackTrace(true); +#endif + ctxt.Post(delegate (object ignore) + { + UIThread.MustBeCalledFromUIThread(); + ourUIQueue.Enqueue(action); + if (ourIsReentrancy) return; + ourIsReentrancy = true; + try + { + while (ourUIQueue.Count > 0) + { + try + { + var a = ourUIQueue.Dequeue(); + a(); + } +#if DEBUG + catch (Exception e) + { + // swallow, random exceptions should not kill process + Debug.Assert(false, string.Format("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 + + } + } + finally + { + ourIsReentrancy = false; + } + }, null); + + } + + /// + /// 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. + public static readonly string WrappedStacktraceKey = "$$Microsoft.VisualStudio.Package.UIThread.WrappedStacktraceKey$$"; + + public static void RunSync(Action a) + { + if (isUnitTestingMode) + { + a(); + return; + } + Exception exn = null; + Debug.Assert(ctxt != null, "The SynchronizationContext must be captured before calling this method"); + // Send on UI thread will execute immediately. + ctxt.Send(ignore => + { + try + { + UIThread.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; + } + } + + /// + /// Local JoinableTaskContext + /// ensuring non-reentrancy. + /// + private static JoinableTaskContext jtc = null; + private static JoinableTaskFactory JTF + { + get + { + if (jtc == null) + { + JoinableTaskContext j = null; + if (VsTaskLibraryHelper.ServiceInstance == null) + { + j = new JoinableTaskContext(); + } + else + { + j = ThreadHelper.JoinableTaskContext; + } + Interlocked.CompareExchange(ref jtc, j, null); + } + + return jtc.Factory; + } + } + + /// + /// Performs a callback on the UI thread and blocks until it is done, using the VS mechanism for + /// ensuring non-reentrancy. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal static T DoOnUIThread(Func callback) + { + return JTF.Run(async delegate + { + await JTF.SwitchToMainThreadAsync(); + return callback(); + }); + } + + /// + /// Performs a callback on the UI thread and blocks until it is done, using the VS mechanism for + /// ensuring non-reentrancy. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + internal static void DoOnUIThread(Action callback) + { + JTF.Run(async delegate + { + await JTF.SwitchToMainThreadAsync(); + callback(); + }); + } + } +} \ No newline at end of file diff --git a/vsintegration/src/FSharp.ProjectSystem.FSharp/ProjectSystem.fsproj b/vsintegration/src/FSharp.ProjectSystem.FSharp/ProjectSystem.fsproj index 47bcceed324..7af3d3e0bee 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/ProjectSystem.fsproj +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/ProjectSystem.fsproj @@ -40,7 +40,6 @@ -