diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/ITestFramework.cs b/src/AddIns/Analysis/UnitTesting/Model/ITestFramework.cs similarity index 100% rename from src/AddIns/Analysis/UnitTesting/Frameworks/ITestFramework.cs rename to src/AddIns/Analysis/UnitTesting/Model/ITestFramework.cs diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/ITestService.cs b/src/AddIns/Analysis/UnitTesting/Service/ITestService.cs similarity index 100% rename from src/AddIns/Analysis/UnitTesting/Frameworks/ITestService.cs rename to src/AddIns/Analysis/UnitTesting/Service/ITestService.cs diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/SDTestService.cs b/src/AddIns/Analysis/UnitTesting/Service/SDTestService.cs similarity index 100% rename from src/AddIns/Analysis/UnitTesting/Frameworks/SDTestService.cs rename to src/AddIns/Analysis/UnitTesting/Service/SDTestService.cs diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDescriptor.cs b/src/AddIns/Analysis/UnitTesting/Service/TestFrameworkDescriptor.cs similarity index 100% rename from src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDescriptor.cs rename to src/AddIns/Analysis/UnitTesting/Service/TestFrameworkDescriptor.cs diff --git a/src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDoozer.cs b/src/AddIns/Analysis/UnitTesting/Service/TestFrameworkDoozer.cs similarity index 100% rename from src/AddIns/Analysis/UnitTesting/Frameworks/TestFrameworkDoozer.cs rename to src/AddIns/Analysis/UnitTesting/Service/TestFrameworkDoozer.cs diff --git a/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj b/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj index 3eede71d50..dc4cbf9f4f 100644 --- a/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj +++ b/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj @@ -70,14 +70,14 @@ - + + - @@ -105,9 +105,9 @@ - - - + + + @@ -181,7 +181,7 @@ - + diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin b/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin index d9e5a12b87..dcb3047376 100755 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin @@ -62,6 +62,8 @@ class="ICSharpCode.SharpDevelop.Parser.AssemblyParserService"/> + diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index ffa7493b77..872ae82fee 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -411,6 +411,7 @@ + @@ -746,6 +747,7 @@ + diff --git a/src/Main/Base/Project/Src/Services/IShutdownService.cs b/src/Main/Base/Project/Src/Services/IShutdownService.cs new file mode 100644 index 0000000000..6f5bd90224 --- /dev/null +++ b/src/Main/Base/Project/Src/Services/IShutdownService.cs @@ -0,0 +1,63 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace ICSharpCode.SharpDevelop +{ + /// + /// Service that manages the IDE shutdown, and any questions. + /// + public interface IShutdownService + { + /// + /// Attemps to close the IDE. + /// + /// + /// This method will + /// - Check if was called and abort the shutdown if it was. + /// - Prompt the user to save the open files. The user has the option to cancel the shutdown at that point. + /// - Closes the solution. + /// - Signals the . + /// - Disposes pads + /// - Wait for background tasks () to finish. + /// - Disposes services + /// - Saves the PropertyService + /// + /// This method must be called on the main thread. + /// + bool Shutdown(); + + /// + /// Prevents shutdown with the following reason. + /// Dispose the returned value to allow shutdown again. + /// + /// The reason. This parameter will be passed through the StringParser when the reason is displayed to the user. + /// Shutdown is already in progress + /// This method is thread-safe. + IDisposable PreventShutdown(string reason); + + /// + /// Gets the current reason that prevents shutdown. + /// If there isn't any reason, returns null. + /// If there are multiple reasons, this returns one of them. + /// + /// This method is thread-safe. + string CurrentReasonPreventingShutdown { get; } + + /// + /// Gets a cancellation token that gets signalled when SharpDevelop is shutting down. + /// + CancellationToken ShutdownToken { get; } + + /// + /// Adds a background task on which SharpDevelop should wait on shutdown. + /// + /// Use this method for tasks that asynchronously write state to disk and should not be + /// interrupted by SharpDevelop closing down. + /// + void AddBackgroundTask(Task task); + } +} diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs b/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs index 86119ae365..53d0a7b9b6 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; - using ICSharpCode.Core; using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.TypeSystem; @@ -16,6 +15,7 @@ using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.NRefactory.Utils; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Project; +using ICSharpCode.SharpDevelop.Util; namespace ICSharpCode.SharpDevelop.Parser { @@ -101,7 +101,7 @@ namespace ICSharpCode.SharpDevelop.Parser { if (cacheFileName == null) return Task.FromResult(null); - return Task.Run( + Task task = IOTaskScheduler.Factory.StartNew( delegate { pc = pc.RemoveAssemblyReferences(pc.AssemblyReferences); int serializableFileCount = 0; @@ -121,7 +121,9 @@ namespace ICSharpCode.SharpDevelop.Parser } else { RemoveCache(cacheFileName); } - }); + }, SD.ShutdownService.ShutdownToken); + SD.ShutdownService.AddBackgroundTask(task); + return task; } static bool IsSerializable(IUnresolvedFile unresolvedFile) diff --git a/src/Main/Base/Project/Src/Services/SD.cs b/src/Main/Base/Project/Src/Services/SD.cs index 4ab15c1a06..614d9568b0 100644 --- a/src/Main/Base/Project/Src/Services/SD.cs +++ b/src/Main/Base/Project/Src/Services/SD.cs @@ -146,5 +146,9 @@ namespace ICSharpCode.SharpDevelop public static IAddInTree AddInTree { get { return GetRequiredService(); } } + + public static IShutdownService ShutdownService { + get { return GetRequiredService(); } + } } } diff --git a/src/Main/Base/Project/Src/Util/IOTaskScheduler.cs b/src/Main/Base/Project/Src/Util/IOTaskScheduler.cs new file mode 100644 index 0000000000..3f765c0ce5 --- /dev/null +++ b/src/Main/Base/Project/Src/Util/IOTaskScheduler.cs @@ -0,0 +1,20 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ICSharpCode.SharpDevelop.Util +{ + /// + /// Scheduler for IO-intensive tasks. + /// + public class IOTaskScheduler + { + // TODO: use a limited-concurrency scheduler instead + public static TaskFactory Factory { + get { return Task.Factory; } + } + } +} diff --git a/src/Main/SharpDevelop/Parser/AssemblyParserService.cs b/src/Main/SharpDevelop/Parser/AssemblyParserService.cs index 996a8dcabb..1e9da54474 100644 --- a/src/Main/SharpDevelop/Parser/AssemblyParserService.cs +++ b/src/Main/SharpDevelop/Parser/AssemblyParserService.cs @@ -11,13 +11,13 @@ using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using System.Xml; - using ICSharpCode.Core; using ICSharpCode.NRefactory.Documentation; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.NRefactory.Utils; using ICSharpCode.SharpDevelop.Project; +using ICSharpCode.SharpDevelop.Util; using Mono.Cecil; namespace ICSharpCode.SharpDevelop.Parser @@ -160,7 +160,9 @@ namespace ICSharpCode.SharpDevelop.Parser } } l.CancellationToken = cancellationToken; + l.InterningProvider = null; pc = l.LoadAssembly(asm); + //SaveToCacheAsync(cacheFileName, lastWriteTime, pc).FireAndForget(); SaveToCache(cacheFileName, lastWriteTime, pc); return pc; } @@ -270,6 +272,18 @@ namespace ICSharpCode.SharpDevelop.Parser } } + Task SaveToCacheAsync(string cacheFileName, DateTime lastWriteTime, IUnresolvedAssembly pc) + { + if (cacheFileName == null) + return Task.FromResult(null); + + // Call SaveToCache on a background task: + var shutdownService = SD.ShutdownService; + var task = IOTaskScheduler.Factory.StartNew(delegate { SaveToCache(cacheFileName, lastWriteTime, pc); }, shutdownService.ShutdownToken); + shutdownService.AddBackgroundTask(task); + return task; + } + void SaveToCache(string cacheFileName, DateTime lastWriteTime, IUnresolvedAssembly pc) { if (cacheFileName == null) diff --git a/src/Main/SharpDevelop/Sda/CallHelper.cs b/src/Main/SharpDevelop/Sda/CallHelper.cs index 9b28f5c1d3..91f5bf7bf1 100644 --- a/src/Main/SharpDevelop/Sda/CallHelper.cs +++ b/src/Main/SharpDevelop/Sda/CallHelper.cs @@ -180,8 +180,11 @@ namespace ICSharpCode.SharpDevelop.Sda } finally { LoggingService.Info("Unloading services..."); try { + // see IShutdownService.Shutdown for a description of the shut down procedure WorkbenchSingleton.OnWorkbenchUnloaded(); var propertyService = SD.PropertyService; + var shutdownService = (ShutdownService)SD.ShutdownService; + shutdownService.WaitForBackgroundTasks(); ((IDisposable)SD.Services).Dispose(); // dispose all services propertyService.Save(); } catch (Exception ex) { diff --git a/src/Main/SharpDevelop/SharpDevelop.csproj b/src/Main/SharpDevelop/SharpDevelop.csproj index af6d157638..80efdc9641 100644 --- a/src/Main/SharpDevelop/SharpDevelop.csproj +++ b/src/Main/SharpDevelop/SharpDevelop.csproj @@ -98,6 +98,7 @@ + diff --git a/src/Main/SharpDevelop/Workbench/ShutdownService.cs b/src/Main/SharpDevelop/Workbench/ShutdownService.cs new file mode 100644 index 0000000000..237822ede3 --- /dev/null +++ b/src/Main/SharpDevelop/Workbench/ShutdownService.cs @@ -0,0 +1,84 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using ICSharpCode.NRefactory.Utils; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + sealed class ShutdownService : IShutdownService + { + CancellationTokenSource cts = new CancellationTokenSource(); + + public CancellationToken ShutdownToken { + get { return cts.Token; } + } + + internal void SignalShutdownToken() + { + cts.Cancel(); + } + + public bool Shutdown() + { + SD.Workbench.MainWindow.Close(); + return SD.Workbench.WorkbenchLayout == null; + } + + #region PreventShutdown + List reasonsPreventingShutdown = new List(); + + public IDisposable PreventShutdown(string reason) + { + lock (reasonsPreventingShutdown) { + reasonsPreventingShutdown.Add(reason); + } + return new CallbackOnDispose( + delegate { + lock (reasonsPreventingShutdown) { + reasonsPreventingShutdown.Remove(reason); + } + }); + } + + public string CurrentReasonPreventingShutdown { + get { + lock (reasonsPreventingShutdown) { + return reasonsPreventingShutdown.FirstOrDefault(); + } + } + } + #endregion + + #region Background Tasks + int outstandingBackgroundTasks; + ManualResetEventSlim backgroundTaskEvent = new ManualResetEventSlim(true); + + public void AddBackgroundTask(Task task) + { + backgroundTaskEvent.Reset(); + Interlocked.Increment(ref outstandingBackgroundTasks); + task.ContinueWith( + delegate { + if (Interlocked.Decrement(ref outstandingBackgroundTasks) == 0) { + backgroundTaskEvent.Set(); + } + }); + } + + internal void WaitForBackgroundTasks() + { + if (!backgroundTaskEvent.IsSet) { + SD.LoggingService.Info("Waiting for background tasks to finish..."); + backgroundTaskEvent.Wait(); + SD.LoggingService.Info("Background tasks have finished."); + } + } + #endregion + } +} diff --git a/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs b/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs index b59ff1c684..fcb5ac6b91 100644 --- a/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs +++ b/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs @@ -561,8 +561,11 @@ namespace ICSharpCode.SharpDevelop.Workbench { base.OnClosing(e); if (!e.Cancel) { - if (Project.ProjectService.IsBuilding) { - MessageService.ShowMessage(StringParser.Parse("${res:MainWindow.CannotCloseWithBuildInProgressMessage}")); + // see IShutdownService.Shutdown() for a description of the shutdown procedure + + var shutdownService = (ShutdownService)SD.ShutdownService; + if (shutdownService.CurrentReasonPreventingShutdown != null) { + MessageService.ShowMessage(StringParser.Parse(shutdownService.CurrentReasonPreventingShutdown)); e.Cancel = true; return; } @@ -586,6 +589,7 @@ namespace ICSharpCode.SharpDevelop.Workbench this.WorkbenchLayout = null; + shutdownService.SignalShutdownToken(); foreach (PadDescriptor padDescriptor in this.PadContentCollection) { padDescriptor.Dispose(); }