Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions vsintegration/src/FSharp.ProjectSystem.Base/Project/UIThread.cs
Original file line number Diff line number Diff line change
@@ -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<Action> ourUIQueue = new Queue<Action>();
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);

}

/// <summary>
/// RunSync puts orignal exception stacktrace to Exception.Data by this key if action throws on UI thread
/// </summary>
/// 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;
}
}

/// <summary>
/// Local JoinableTaskContext
/// ensuring non-reentrancy.
/// </summary>
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;
}
}

/// <summary>
/// Performs a callback on the UI thread and blocks until it is done, using the VS mechanism for
/// ensuring non-reentrancy.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
internal static T DoOnUIThread<T>(Func<T> callback)
{
return JTF.Run<T>(async delegate
{
await JTF.SwitchToMainThreadAsync();
return callback();
});
}

/// <summary>
/// Performs a callback on the UI thread and blocks until it is done, using the VS mechanism for
/// ensuring non-reentrancy.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
internal static void DoOnUIThread(Action callback)
{
JTF.Run(async delegate
{
await JTF.SwitchToMainThreadAsync();
callback();
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
<ItemGroup>
<ProjectReference Include="$(FSharpSourcesRoot)\fsharp\FSharp.Core\FSharp.Core.fsproj" />
<ProjectReference Include="$(FSharpSourcesRoot)\fsharp\FSharp.Compiler.Private\FSharp.Compiler.Private.fsproj" />
<ProjectReference Include="..\FSharp.LanguageService\FSharp.LanguageService.fsproj" />
<ProjectReference Include="..\FSharp.VS.FSI\FSharp.VS.FSI.fsproj" />
<ProjectReference Include="..\FSharp.ProjectSystem.Base\Project\ProjectSystem.Base.csproj" />
<ProjectReference Include="..\FSharp.ProjectSystem.PropertyPages\FSharp.PropertiesPages.vbproj" />
Expand Down