diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index f272fe4750..5564195a8d 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -227,6 +227,7 @@ namespace ICSharpCode.NRefactory.TypeSystem } AddToTypeSystemTranslationTable(this.currentAssembly, assemblyDefinition); + currentAssembly.Freeze(); var result = this.currentAssembly; this.currentAssembly = null; diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 872ae82fee..eabd7c0c11 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -745,11 +745,13 @@ + + diff --git a/src/Main/Base/Project/Src/Services/IShutdownService.cs b/src/Main/Base/Project/Src/Services/IShutdownService.cs index 6f5bd90224..21a06cb440 100644 --- a/src/Main/Base/Project/Src/Services/IShutdownService.cs +++ b/src/Main/Base/Project/Src/Services/IShutdownService.cs @@ -49,9 +49,20 @@ namespace ICSharpCode.SharpDevelop /// /// Gets a cancellation token that gets signalled when SharpDevelop is shutting down. + /// + /// This cancellation token may be used to stop background calculations. /// CancellationToken ShutdownToken { get; } + /// + /// Gets a cancellation token that gets signalled a couple of seconds after the ShutdownToken. + /// + /// This cancellation token may be used to stop background calculations that should run + /// for a limited time after SharpDevelop is closed (e.g. saving state in caches + /// - work that should better run even though we're shutting down, but shouldn't take too long either) + /// + CancellationToken DelayedShutdownToken { get; } + /// /// Adds a background task on which SharpDevelop should wait on shutdown. /// diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs b/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs index 53d0a7b9b6..327001fc05 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs @@ -121,7 +121,7 @@ namespace ICSharpCode.SharpDevelop.Parser } else { RemoveCache(cacheFileName); } - }, SD.ShutdownService.ShutdownToken); + }, SD.ShutdownService.DelayedShutdownToken); SD.ShutdownService.AddBackgroundTask(task); return task; } diff --git a/src/Main/Base/Project/Src/Util/CustomThreadPoolTaskScheduler.cs b/src/Main/Base/Project/Src/Util/CustomThreadPoolTaskScheduler.cs new file mode 100644 index 0000000000..d3dcbd7686 --- /dev/null +++ b/src/Main/Base/Project/Src/Util/CustomThreadPoolTaskScheduler.cs @@ -0,0 +1,67 @@ +// 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.Util +{ + /// + /// A task scheduler that manages its own thread pool. + /// + public class CustomThreadPoolTaskScheduler : SimpleTaskScheduler + { + int currentThreadCount; + readonly int maxThreadCount; + + public CustomThreadPoolTaskScheduler(int maxThreadCount) + { + this.maxThreadCount = maxThreadCount; + } + + public override int MaximumConcurrencyLevel { + get { return maxThreadCount; } + } + + protected override void QueueTask(Task task) + { + base.QueueTask(task); + if (IncrementThreadCount()) { + // Successfully incremented the thread count, we may start a thread + StartThread(RunThread); + return; + } + } + + protected virtual void StartThread(ThreadStart start) + { + var t = new Thread(RunThread); + t.IsBackground = true; + t.Start(); + } + + bool IncrementThreadCount() + { + int c = Volatile.Read(ref currentThreadCount); + while (c < maxThreadCount) { + if (Interlocked.CompareExchange(ref currentThreadCount, c + 1, c) == c) { + return true; + } + } + return false; + } + + void RunThread() + { + do { + // Run tasks while they are available: + while (TryRunNextTask()); + // Decrement the thread count: + Interlocked.Decrement(ref currentThreadCount); + // Tasks might have been added while we were decrementing the thread count, + // so if the queue isn't empty anymore, resume this thread + } while(ScheduledTaskCount > 0 && IncrementThreadCount()); + } + } +} diff --git a/src/Main/Base/Project/Src/Util/IOTaskScheduler.cs b/src/Main/Base/Project/Src/Util/IOTaskScheduler.cs index 3f765c0ce5..1399f7a377 100644 --- a/src/Main/Base/Project/Src/Util/IOTaskScheduler.cs +++ b/src/Main/Base/Project/Src/Util/IOTaskScheduler.cs @@ -12,9 +12,17 @@ namespace ICSharpCode.SharpDevelop.Util /// public class IOTaskScheduler { - // TODO: use a limited-concurrency scheduler instead + static readonly CustomThreadPoolTaskScheduler scheduler = new CustomThreadPoolTaskScheduler( + Math.Min(Environment.ProcessorCount, 2)); + + static readonly TaskFactory factory = new TaskFactory(scheduler); + + public static TaskScheduler Scheduler { + get { return scheduler; } + } + public static TaskFactory Factory { - get { return Task.Factory; } + get { return factory; } } } } diff --git a/src/Main/Base/Project/Src/Util/SimpleTaskScheduler.cs b/src/Main/Base/Project/Src/Util/SimpleTaskScheduler.cs new file mode 100644 index 0000000000..0da2b9f97b --- /dev/null +++ b/src/Main/Base/Project/Src/Util/SimpleTaskScheduler.cs @@ -0,0 +1,82 @@ +// 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.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ICSharpCode.SharpDevelop.Util +{ + /// + /// A simple scheduler that adds tasks to a queue. + /// This scheduler does not create any worker threads on its own, + /// but requires external code to call . + /// + public class SimpleTaskScheduler : TaskScheduler, IDisposable + { + [ThreadStatic] + static SimpleTaskScheduler activeScheduler; + + BlockingCollection queue = new BlockingCollection(); + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + return activeScheduler == this && base.TryExecuteTask(task); + } + + protected override void QueueTask(Task task) + { + queue.Add(task); + } + + protected override IEnumerable GetScheduledTasks() + { + return queue; + } + + protected int ScheduledTaskCount { + get { return queue.Count; } + } + + /// + /// Runs the next task in the queue. + /// If no task is available, this method will block. + /// + /// Cancellation token that can be used to cancel + /// waiting for a task to become available. It cannot be used to cancel task execution! + public void RunNextTask(CancellationToken cancellationToken = default(CancellationToken)) + { + Task task = queue.Take(cancellationToken); + RunTask(task); + } + + public bool TryRunNextTask() + { + Task task; + if (queue.TryTake(out task)) { + RunTask(task); + return true; + } else { + return false; + } + } + + void RunTask(Task task) + { + var oldActiveScheduler = activeScheduler; + activeScheduler = this; + try { + base.TryExecuteTask(task); + } finally { + activeScheduler = oldActiveScheduler; + } + } + + public virtual void Dispose() + { + queue.Dispose(); + } + } +} diff --git a/src/Main/SharpDevelop/Parser/AssemblyParserService.cs b/src/Main/SharpDevelop/Parser/AssemblyParserService.cs index 1e9da54474..6d024387d9 100644 --- a/src/Main/SharpDevelop/Parser/AssemblyParserService.cs +++ b/src/Main/SharpDevelop/Parser/AssemblyParserService.cs @@ -160,10 +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); + SaveToCacheAsync(cacheFileName, lastWriteTime, pc).FireAndForget(); + //SaveToCache(cacheFileName, lastWriteTime, pc); return pc; } diff --git a/src/Main/SharpDevelop/Workbench/ShutdownService.cs b/src/Main/SharpDevelop/Workbench/ShutdownService.cs index 237822ede3..9b5513f2d9 100644 --- a/src/Main/SharpDevelop/Workbench/ShutdownService.cs +++ b/src/Main/SharpDevelop/Workbench/ShutdownService.cs @@ -13,15 +13,21 @@ namespace ICSharpCode.SharpDevelop.Workbench { sealed class ShutdownService : IShutdownService { - CancellationTokenSource cts = new CancellationTokenSource(); + CancellationTokenSource shutdownCTS = new CancellationTokenSource(); + CancellationTokenSource delayedShutdownCTS = new CancellationTokenSource(); public CancellationToken ShutdownToken { - get { return cts.Token; } + get { return shutdownCTS.Token; } + } + + public CancellationToken DelayedShutdownToken { + get { return delayedShutdownCTS.Token; } } internal void SignalShutdownToken() { - cts.Cancel(); + shutdownCTS.Cancel(); + delayedShutdownCTS.CancelAfter(2000); } public bool Shutdown()