From 28b4aa68768facd56b10047a4be3dfed847bf24e Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Fri, 3 Jun 2016 11:56:50 -0700 Subject: [PATCH 1/2] Persist the low-memory-mode option to the registry --- ...stractLocalUserRegistryOptionSerializer.cs | 96 +++++++++++++++++++ .../AbstractSettingStoreOptionSerializer.cs | 93 ------------------ .../Impl/Options/InternalOptionSerializer.cs | 56 ++++------- .../Core/Impl/ServicesVisualStudioImpl.csproj | 2 +- .../OptionPages/ForceLowMemoryMode.cs | 44 +++++---- .../OptionPages/ForceLowMemoryMode_Options.cs | 31 ++++++ .../OptionPages/InternalFeaturesOnOffPage.cs | 45 ++------- .../VisualStudioDiagnosticsWindow.csproj | 1 + .../VisualStudioDiagnosticsWindowPackage.cs | 10 ++ .../source.extension.vsixmanifest | 1 + 10 files changed, 191 insertions(+), 188 deletions(-) create mode 100644 src/VisualStudio/Core/Impl/Options/AbstractLocalUserRegistryOptionSerializer.cs delete mode 100644 src/VisualStudio/Core/Impl/Options/AbstractSettingStoreOptionSerializer.cs create mode 100644 src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs diff --git a/src/VisualStudio/Core/Impl/Options/AbstractLocalUserRegistryOptionSerializer.cs b/src/VisualStudio/Core/Impl/Options/AbstractLocalUserRegistryOptionSerializer.cs new file mode 100644 index 0000000000000..d17bf7db8ebd8 --- /dev/null +++ b/src/VisualStudio/Core/Impl/Options/AbstractLocalUserRegistryOptionSerializer.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Options.Providers; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.Win32; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options +{ + internal abstract class AbstractLocalUserRegistryOptionSerializer : ForegroundThreadAffinitizedObject, IOptionSerializer + { + /// + /// An object to gate access to . + /// + private readonly object _gate = new object(); + private readonly RegistryKey _registryKey; + + protected abstract string GetCollectionPathForOption(OptionKey key); + + public AbstractLocalUserRegistryOptionSerializer(IServiceProvider serviceProvider) + : base(assertIsForeground: true) // The VSRegistry.RegistryRoot call requires being on the UI thread or else it will marshal and risk deadlock + { + this._registryKey = VSRegistry.RegistryRoot(serviceProvider, __VsLocalRegistryType.RegType_UserSettings, writable: true); + } + + bool IOptionSerializer.TryFetch(OptionKey optionKey, out object value) + { + var collectionPath = GetCollectionPathForOption(optionKey); + if (collectionPath == null) + { + value = null; + return false; + } + + lock (_gate) + { + using (var subKey = this._registryKey.OpenSubKey(collectionPath)) + { + if (subKey == null) + { + value = null; + return false; + } + + // Options that are of type bool have to be serialized as integers + if (optionKey.Option.Type == typeof(bool)) + { + value = subKey.GetValue(optionKey.Option.Name, defaultValue: (bool)optionKey.Option.DefaultValue ? 1 : 0).Equals(1); + return true; + } + else + { + // Otherwise we can just store normally + value = subKey.GetValue(optionKey.Option.Name, defaultValue: optionKey.Option.DefaultValue); + return true; + } + } + } + } + + bool IOptionSerializer.TryPersist(OptionKey optionKey, object value) + { + if (this._registryKey == null) + { + throw new InvalidOperationException(); + } + + var collectionPath = GetCollectionPathForOption(optionKey); + if (collectionPath == null) + { + return false; + } + + lock (_gate) + { + using (var subKey = this._registryKey.CreateSubKey(collectionPath)) + { + // Options that are of type bool have to be serialized as integers + if (optionKey.Option.Type == typeof(bool)) + { + subKey.SetValue(optionKey.Option.Name, (bool)value ? 1 : 0, RegistryValueKind.DWord); + return true; + } + else + { + subKey.SetValue(optionKey.Option.Name, value); + return true; + } + } + } + } + } +} diff --git a/src/VisualStudio/Core/Impl/Options/AbstractSettingStoreOptionSerializer.cs b/src/VisualStudio/Core/Impl/Options/AbstractSettingStoreOptionSerializer.cs deleted file mode 100644 index bcd282f6fe838..0000000000000 --- a/src/VisualStudio/Core/Impl/Options/AbstractSettingStoreOptionSerializer.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.Win32; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options -{ - internal abstract class AbstractSettingStoreOptionSerializer : ForegroundThreadAffinitizedObject, IOptionSerializer - { - // The gate object guards the RegistryKey - protected readonly object Gate = new object(); - protected readonly RegistryKey RegistryKey; - - protected abstract Tuple GetCollectionPathAndPropertyNameForOption(IOption key, string languageName); - - public AbstractSettingStoreOptionSerializer(IServiceProvider serviceProvider) - : base(assertIsForeground: true) // The VSRegistry.RegistryRoot call requires being on the UI thread or else it will marshal and risk deadlock - { - this.RegistryKey = VSRegistry.RegistryRoot(serviceProvider, __VsLocalRegistryType.RegType_UserSettings, writable: true); - } - - public virtual bool TryFetch(OptionKey optionKey, out object value) - { - return TryFetch(optionKey, (r, k, o) => r.GetValue(k, defaultValue: (bool)o.DefaultValue ? 1 : 0).Equals(1), out value); - } - - public virtual bool TryPersist(OptionKey optionKey, object value) - { - return TryPersist(optionKey, value, (r, k, o, v) => r.SetValue(k, (bool)v ? 1 : 0, RegistryValueKind.DWord)); - } - - protected bool TryFetch(OptionKey optionKey, Func valueGetter, out object value) - { - if (this.RegistryKey == null) - { - throw new InvalidOperationException(); - } - - var collectionPathAndPropertyName = GetCollectionPathAndPropertyNameForOption(optionKey.Option, optionKey.Language); - if (collectionPathAndPropertyName == null) - { - value = null; - return false; - } - - lock (Gate) - { - using (var subKey = this.RegistryKey.OpenSubKey(collectionPathAndPropertyName.Item1)) - { - if (subKey == null) - { - value = null; - return false; - } - - value = valueGetter(subKey, collectionPathAndPropertyName.Item2, optionKey.Option); - return true; - } - } - } - - protected bool TryPersist(OptionKey optionKey, object value, Action valueSetter) - { - // We ignore languageName, since the current use of this class is only for - // language-specific options that apply to a single language. The underlying option - // service has already ensured the languageName is right, so we'll drop it on the floor. - if (this.RegistryKey == null) - { - throw new InvalidOperationException(); - } - - var collectionPathAndPropertyName = GetCollectionPathAndPropertyNameForOption(optionKey.Option, optionKey.Language); - if (collectionPathAndPropertyName == null) - { - return false; - } - - lock (Gate) - { - using (var subKey = this.RegistryKey.CreateSubKey(collectionPathAndPropertyName.Item1)) - { - valueSetter(subKey, collectionPathAndPropertyName.Item2, optionKey.Option, value); - return true; - } - } - } - } -} diff --git a/src/VisualStudio/Core/Impl/Options/InternalOptionSerializer.cs b/src/VisualStudio/Core/Impl/Options/InternalOptionSerializer.cs index 8124387957515..3e3821218c8f5 100644 --- a/src/VisualStudio/Core/Impl/Options/InternalOptionSerializer.cs +++ b/src/VisualStudio/Core/Impl/Options/InternalOptionSerializer.cs @@ -21,7 +21,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options CacheOptions.FeatureName, InternalDiagnosticsOptions.OptionName, InternalSolutionCrawlerOptions.OptionName), Shared] - internal class InternalOptionSerializer : AbstractSettingStoreOptionSerializer + internal class InternalOptionSerializer : AbstractLocalUserRegistryOptionSerializer { [ImportingConstructor] public InternalOptionSerializer(SVsServiceProvider serviceProvider) @@ -29,62 +29,38 @@ public InternalOptionSerializer(SVsServiceProvider serviceProvider) { } - protected override Tuple GetCollectionPathAndPropertyNameForOption(IOption key, string languageName) + protected override string GetCollectionPathForOption(OptionKey key) { - if (key.Feature == EditorComponentOnOffOptions.OptionName) + if (key.Option.Feature == EditorComponentOnOffOptions.OptionName) { - return Tuple.Create(@"Roslyn\Internal\OnOff\Components", key.Name); + return @"Roslyn\Internal\OnOff\Components"; } - else if (key.Feature == InternalFeatureOnOffOptions.OptionName) + else if (key.Option.Feature == InternalFeatureOnOffOptions.OptionName) { - return Tuple.Create(@"Roslyn\Internal\OnOff\Features", key.Name); + return @"Roslyn\Internal\OnOff\Features"; } - else if (key.Feature == PerformanceFunctionIdOptionsProvider.Name) + else if (key.Option.Feature == PerformanceFunctionIdOptionsProvider.Name) { - return Tuple.Create(@"Roslyn\Internal\Performance\FunctionId", key.Name); + return @"Roslyn\Internal\Performance\FunctionId"; } - else if (key.Feature == LoggerOptions.FeatureName) + else if (key.Option.Feature == LoggerOptions.FeatureName) { - return Tuple.Create(@"Roslyn\Internal\Performance\Logger", key.Name); + return @"Roslyn\Internal\Performance\Logger"; } - else if (key.Feature == InternalDiagnosticsOptions.OptionName) + else if (key.Option.Feature == InternalDiagnosticsOptions.OptionName) { - return Tuple.Create(@"Roslyn\Internal\Diagnostics", key.Name); + return @"Roslyn\Internal\Diagnostics"; } - else if (key.Feature == InternalSolutionCrawlerOptions.OptionName) + else if (key.Option.Feature == InternalSolutionCrawlerOptions.OptionName) { - return Tuple.Create(@"Roslyn\Internal\SolutionCrawler", key.Name); + return @"Roslyn\Internal\SolutionCrawler"; } - else if (key.Feature == CacheOptions.FeatureName) + else if (key.Option.Feature == CacheOptions.FeatureName) { - return Tuple.Create(@"Roslyn\Internal\Performance\Cache", key.Name); + return @"Roslyn\Internal\Performance\Cache"; } throw ExceptionUtilities.Unreachable; } - - public override bool TryFetch(OptionKey optionKey, out object value) - { - switch (optionKey.Option.Feature) - { - case CacheOptions.FeatureName: - case InternalSolutionCrawlerOptions.OptionName: - return TryFetch(optionKey, (r, k, o) => r.GetValue(k, defaultValue: o.DefaultValue), out value); - } - - return base.TryFetch(optionKey, out value); - } - - public override bool TryPersist(OptionKey optionKey, object value) - { - switch (optionKey.Option.Feature) - { - case CacheOptions.FeatureName: - case InternalSolutionCrawlerOptions.OptionName: - return TryPersist(optionKey, value, (r, k, o, v) => r.SetValue(k, v, o.Type == typeof(int) ? RegistryValueKind.DWord : RegistryValueKind.QWord)); - } - - return base.TryPersist(optionKey, value); - } } } diff --git a/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj b/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj index 693794ed5db78..953a405263a60 100644 --- a/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj +++ b/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj @@ -244,7 +244,7 @@ - + diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs index 0819647e6177a..997c913dfdf36 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs @@ -3,40 +3,46 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Options; namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages { - internal class ForceLowMemoryMode + internal sealed partial class ForceLowMemoryMode { + private readonly IOptionService _optionService; private MemoryHogger _hogger; - public static readonly ForceLowMemoryMode Instance = new ForceLowMemoryMode(); - - private ForceLowMemoryMode() + public ForceLowMemoryMode(IOptionService optionService) { + _optionService = optionService; + + optionService.OptionChanged += Options_OptionChanged; + + RefreshFromSettings(); } - public int Size { get; set; } = 500; // default to 500 MB + private void Options_OptionChanged(object sender, OptionChangedEventArgs e) + { + if (e.Option.Feature == ForceLowMemoryMode.OptionName) + { + RefreshFromSettings(); + } + } - public bool Enabled + private void RefreshFromSettings() { - get + var enabled = _optionService.GetOption(Enabled); + + if (_hogger != null) { - return _hogger != null; + _hogger.Cancel(); + _hogger = null; } - set + if (enabled) { - if (value && _hogger == null) - { - _hogger = new MemoryHogger(); - var ignore = _hogger.PopulateAndMonitorAsync(this.Size); - } - else if (!value && _hogger != null) - { - _hogger.Cancel(); - _hogger = null; - } + _hogger = new MemoryHogger(); + var ignore = _hogger.PopulateAndMonitorAsync(_optionService.GetOption(SizeInMegabytes)); } } diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs new file mode 100644 index 0000000000000..2905a375d7108 --- /dev/null +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.LanguageServices.Implementation.Options; +using Microsoft.VisualStudio.Shell; + +namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages +{ + internal sealed partial class ForceLowMemoryMode + { + public const string OptionName = nameof(ForceLowMemoryMode); + public static readonly Option Enabled = new Option(OptionName, nameof(Enabled), defaultValue: false); + public static readonly Option SizeInMegabytes = new Option(OptionName, nameof(SizeInMegabytes), defaultValue: 500); + } + + [ExportOptionSerializer(ForceLowMemoryMode.OptionName), Shared] + internal sealed class ForceLowMemoryModeSerializer : AbstractLocalUserRegistryOptionSerializer + { + [ImportingConstructor] + public ForceLowMemoryModeSerializer(SVsServiceProvider serviceProvider) : base(serviceProvider) + { + } + + protected override string GetCollectionPathForOption(OptionKey key) + { + return @"Roslyn\ForceLowMemoryMode"; + } + } +} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalFeaturesOnOffPage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalFeaturesOnOffPage.cs index 5ed986e9cd107..26c14b8d2e454 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalFeaturesOnOffPage.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalFeaturesOnOffPage.cs @@ -35,48 +35,23 @@ public InternalFeaturesOptionsControl(string featureOptionName, IServiceProvider protected override void AddOptions(Panel panel) { - base.AddOptions(panel); - // add force low memory mode option var group = new WrapPanel(); - var lowMemoryMode = ForceLowMemoryMode.Instance; - var cb = CreateBoundCheckBox("Forced Low Memory Mode", lowMemoryMode, "Enabled"); - group.Children.Add(cb); - var tb = CreateBoundTextBox("", lowMemoryMode, "Size"); - group.Children.Add(tb); - var text = new TextBlock() { Text = "MB" }; - group.Children.Add(text); - panel.Children.Add(group); - } - - private CheckBox CreateBoundCheckBox(string content, object source, string sourcePropertyName) - { - var cb = new CheckBox { Content = content }; - var binding = new Binding() - { - Source = source, - Path = new PropertyPath(sourcePropertyName) - }; - - base.AddBinding(cb.SetBinding(CheckBox.IsCheckedProperty, binding)); + var cb = new CheckBox { Content = "Forced Low Memory Mode: allocate" }; + BindToOption(cb, ForceLowMemoryMode.Enabled); + group.Children.Add(cb); - return cb; - } + var textBox = new TextBox { MinWidth = 60 }; + BindToOption(textBox, ForceLowMemoryMode.SizeInMegabytes); + group.Children.Add(textBox); - private TextBox CreateBoundTextBox(string content, object source, string sourcePropertyName) - { - var tb = new TextBox { Text = content }; + group.Children.Add(new TextBlock { Text = "megabytes of extra memory in devenv.exe" }); - var binding = new Binding() - { - Source = source, - Path = new PropertyPath(sourcePropertyName) - }; - - base.AddBinding(tb.SetBinding(TextBox.TextProperty, binding)); + panel.Children.Add(group); - return tb; + // and add the rest of the options + base.AddOptions(panel); } } } diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindow.csproj b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindow.csproj index 3ac32cdd595b2..4f860a12bc1ae 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindow.csproj +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindow.csproj @@ -101,6 +101,7 @@ + diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs index 8e538d946576a..cfd8bb89c7dd0 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs @@ -4,6 +4,9 @@ using System.ComponentModel; using System.ComponentModel.Design; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.Implementation.Options; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -39,8 +42,11 @@ namespace Roslyn.VisualStudio.DiagnosticsWindow [ProvideToolWindow(typeof(DiagnosticsWindow))] [Guid(GuidList.guidVisualStudioDiagnosticsWindowPkgString)] [Description("Roslyn Diagnostics Window")] + [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] public sealed class VisualStudioDiagnosticsWindowPackage : Package { + private ForceLowMemoryMode _forceLowMemoryMode; + /// /// This function is called when the user clicks the menu item that shows the /// tool window. See the Initialize method to see how the menu item is associated to @@ -72,6 +78,10 @@ protected override void Initialize() { base.Initialize(); + var componentModel = (IComponentModel)GetService(typeof(SComponentModel)); + + _forceLowMemoryMode = new ForceLowMemoryMode(componentModel.GetService()); + // Add our command handlers for menu (commands must exist in the .vsct file) OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (null != mcs) diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/source.extension.vsixmanifest b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/source.extension.vsixmanifest index 81112ee54201e..e450b6e8fc219 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/source.extension.vsixmanifest +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/source.extension.vsixmanifest @@ -16,6 +16,7 @@ + From 4b4ba64360b2a4f0a6798cabb0f93247c2ad21c4 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Fri, 3 Jun 2016 17:41:08 -0700 Subject: [PATCH 2/2] Use a cancellation token in MemoryHogger When you cancelled, it would continue to sit around waiting for the Task.Delay to complete. We also set LongRunning since the initial population is indeed long running. --- .../OptionPages/ForceLowMemoryMode.cs | 90 +++++++++++-------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs index 997c913dfdf36..31f2cc86114b5 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; @@ -52,7 +53,7 @@ private class MemoryHogger private const int MonitorDelay = 10000; // 10 seconds private readonly List _blocks = new List(); - private bool _cancelled; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); public MemoryHogger() { @@ -65,55 +66,35 @@ public int Count public void Cancel() { - _cancelled = true; + _cancellationTokenSource.Cancel(); } public Task PopulateAndMonitorAsync(int size) { // run on background thread - return Task.Run(() => this.PopulateAndMonitorWorkerAsync(size)); + return Task.Factory.StartNew(() => this.PopulateAndMonitorWorkerAsync(size), CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap(); } private async Task PopulateAndMonitorWorkerAsync(int size) { try - { - for (int n = 0; n < size && !_cancelled; n++) - { - var block = new byte[BlockSize]; - - // initialize block bits (so the memory actually gets allocated.. silly runtime!) - for (int i = 0; i < BlockSize; i++) - { - block[i] = 0xFF; - } - - _blocks.Add(block); - - // don't hog the thread - await Task.Yield(); - } - } - catch (OutOfMemoryException) - { - } - - // monitor memory to keep it paged in - while (!_cancelled) { try { - // access all block bytes - for (var b = 0; b < _blocks.Count && !_cancelled; b++) + for (int n = 0; n < size; n++) { - var block = _blocks[b]; + _cancellationTokenSource.Token.ThrowIfCancellationRequested(); - byte tmp; - for (int i = 0; i < block.Length; i++) + var block = new byte[BlockSize]; + + // initialize block bits (so the memory actually gets allocated.. silly runtime!) + for (int i = 0; i < BlockSize; i++) { - tmp = block[i]; + block[i] = 0xFF; } + _blocks.Add(block); + // don't hog the thread await Task.Yield(); } @@ -122,16 +103,47 @@ private async Task PopulateAndMonitorWorkerAsync(int size) { } - await Task.Delay(MonitorDelay).ConfigureAwait(true); - } + // monitor memory to keep it paged in + while (true) + { + _cancellationTokenSource.Token.ThrowIfCancellationRequested(); + + try + { + // access all block bytes + for (var b = 0; b < _blocks.Count; b++) + { + _cancellationTokenSource.Token.ThrowIfCancellationRequested(); - _blocks.Clear(); + var block = _blocks[b]; - // force garbage collection - for (int i = 0; i < 5; i++) + byte tmp; + for (int i = 0; i < block.Length; i++) + { + tmp = block[i]; + } + + // don't hog the thread + await Task.Yield(); + } + } + catch (OutOfMemoryException) + { + } + + await Task.Delay(MonitorDelay, _cancellationTokenSource.Token).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { - GC.Collect(GC.MaxGeneration); - GC.WaitForPendingFinalizers(); + _blocks.Clear(); + + // force garbage collection + for (int i = 0; i < 5; i++) + { + GC.Collect(GC.MaxGeneration); + GC.WaitForPendingFinalizers(); + } } } }