mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 18:51:28 +08:00
Compare commits
31 Commits
10.11.87.0
...
v10
Author | SHA1 | Date | |
---|---|---|---|
![]() |
516fd7f235 | ||
![]() |
2ee16c3533 | ||
![]() |
7d22f5c78e | ||
![]() |
3e604ee2fd | ||
![]() |
47e442874c | ||
![]() |
2a8c0cbab1 | ||
![]() |
c26898b49d | ||
![]() |
00c24d06a3 | ||
![]() |
3461f34240 | ||
![]() |
aa1ce08c02 | ||
![]() |
9c230c2da9 | ||
![]() |
21215d0379 | ||
![]() |
7448183791 | ||
![]() |
35edd7dc43 | ||
![]() |
bd178831e3 | ||
![]() |
fe9ec6ad10 | ||
![]() |
6f9ec2e24b | ||
![]() |
c0337e2b19 | ||
![]() |
8a95f48f5a | ||
![]() |
14f3c31265 | ||
![]() |
1bad65378f | ||
![]() |
db3affc67e | ||
![]() |
5ee8b50a92 | ||
![]() |
301beda2a2 | ||
![]() |
628b51a353 | ||
![]() |
f03445bc83 | ||
![]() |
55a2ff5487 | ||
![]() |
0fef7dcf3b | ||
![]() |
19d9702606 | ||
![]() |
a8a9774932 | ||
![]() |
aad0f0e8c3 |
@@ -251,11 +251,13 @@ public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter
|
||||
|
||||
if (exception == null)
|
||||
{
|
||||
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}");
|
||||
if (logger.IsEnabled(LogLevel.Warning))
|
||||
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@ namespace ThingsGateway.Admin.Application;
|
||||
/// <typeparam name="TEntry"></typeparam>
|
||||
public class EventService<TEntry> : IEventService<TEntry>, IDisposable
|
||||
{
|
||||
private ConcurrentDictionary<string, Func<TEntry, Task>> Cache = new();
|
||||
private NonBlockingDictionary<string, Func<TEntry, Task>> Cache = new();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@@ -20,7 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
||||
|
@@ -4,8 +4,8 @@
|
||||
|
||||
<div class="tg-table h-100">
|
||||
|
||||
<Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
||||
DataService="DataService" CreateItemCallback="CreateItemCallback!"
|
||||
<Table Id=@Id TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
||||
DataService="DataService" CreateItemCallback="CreateItemCallback!" RenderMode=RenderMode OnColumnCreating=OnColumnCreating
|
||||
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!"
|
||||
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender
|
||||
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
|
||||
@@ -14,7 +14,7 @@
|
||||
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
|
||||
SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
|
||||
ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
|
||||
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView
|
||||
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView OnColumnVisibleChanged=OnColumnVisibleChanged
|
||||
FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn
|
||||
IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
|
||||
AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
|
||||
@@ -41,6 +41,7 @@
|
||||
DoubleClickToEdit="DoubleClickToEdit"
|
||||
OnDoubleClickCellCallback="OnDoubleClickCellCallback"
|
||||
OnDoubleClickRowCallback="OnDoubleClickRowCallback"
|
||||
RowContentTemplate="RowContentTemplate"
|
||||
OnClickRowCallback="OnClickRowCallback">
|
||||
</Table>
|
||||
</div>
|
||||
|
@@ -13,6 +13,23 @@ namespace ThingsGateway.Admin.Razor;
|
||||
[CascadingTypeParameter(nameof(TItem))]
|
||||
public partial class AdminTable<TItem> where TItem : class, new()
|
||||
{
|
||||
/// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/>
|
||||
[Parameter]
|
||||
public Func<string, bool, Task> OnColumnVisibleChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.OnColumnCreating"/>
|
||||
[Parameter]
|
||||
public Func<List<ITableColumn>, Task> OnColumnCreating { get; set; }
|
||||
/// <inheritdoc cref="Table{TItem}.RenderMode"/>
|
||||
[Parameter]
|
||||
public TableRenderMode RenderMode { get; set; }
|
||||
|
||||
public List<ITableColumn> Columns => Instance?.Columns;
|
||||
|
||||
public IEnumerable<ITableColumn> GetVisibleColumns => Instance?.GetVisibleColumns();
|
||||
public List<TItem> Rows => Instance?.Rows;
|
||||
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
|
||||
@@ -40,6 +57,10 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
/// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; }
|
||||
/// <inheritdoc cref="Table{TItem}.RowContentTemplate"/>
|
||||
[Parameter]
|
||||
public RenderFragment<TableRowContext<TItem>>? RowContentTemplate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, Task>? OnClickRowCallback { get; set; }
|
||||
@@ -146,6 +167,9 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
[Parameter]
|
||||
public IDataService<TItem> DataService { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Id { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.CreateItemCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem> CreateItemCallback { get; set; }
|
||||
|
@@ -72,7 +72,7 @@ public partial class HardwareInfoPage : IDisposable
|
||||
ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)];
|
||||
ChartDataSource.Options.X.Title = Localizer["DateTime"];
|
||||
ChartDataSource.Options.Y.Title = Localizer["Data"];
|
||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz"));
|
||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm"));
|
||||
ChartDataSource.Data.Add(new ChartDataset()
|
||||
{
|
||||
Tension = 0.4f,
|
||||
@@ -116,7 +116,7 @@ public partial class HardwareInfoPage : IDisposable
|
||||
else
|
||||
{
|
||||
var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos();
|
||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz"));
|
||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm"));
|
||||
ChartDataSource.Data[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage);
|
||||
ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage);
|
||||
ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage);
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.1" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.4" />
|
||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
|
||||
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
|
||||
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" />
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
@@ -477,7 +477,7 @@ public class ConcurrentList<T> : IList<T>, IReadOnlyList<T>
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.IndexOf(item);
|
||||
return m_list.LastIndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -82,7 +82,7 @@ public static class ObjectExtensions
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="dic">字典</param>
|
||||
/// <param name="newDic">新字典</param>
|
||||
internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic)
|
||||
internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic)
|
||||
{
|
||||
foreach (var (key, value) in newDic)
|
||||
{
|
||||
|
@@ -66,7 +66,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (Logger?.IsEnabled(LogLevel.Error) == true)
|
||||
Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -176,7 +177,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (jsonLocalizationOptions.IgnoreLocalizerMissing == false)
|
||||
{
|
||||
Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (Logger?.IsEnabled(LogLevel.Information) == true)
|
||||
Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
}
|
||||
_missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}");
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.0" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -205,7 +205,7 @@ public static class ObjectExtensions
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="dic">字典</param>
|
||||
/// <param name="newDic">新字典</param>
|
||||
internal static void AddOrUpdate<T>(this ConcurrentDictionary<string, T> dic, Dictionary<string, T> newDic)
|
||||
internal static void AddOrUpdate<T>(this NonBlockingDictionary<string, T> dic, Dictionary<string, T> newDic)
|
||||
{
|
||||
foreach (var (key, value) in newDic)
|
||||
{
|
||||
|
@@ -94,7 +94,7 @@ public static class AspNetCoreBuilderServiceCollectionExtensions
|
||||
/// <param name="mvcBuilder"></param>
|
||||
/// <param name="configure"></param>
|
||||
/// <returns></returns>
|
||||
public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<ConcurrentDictionary<Type, Type>> configure = default)
|
||||
public static IMvcBuilder AddFromConvertBinding(this IMvcBuilder mvcBuilder, Action<NonBlockingDictionary<Type, Type>> configure = default)
|
||||
{
|
||||
mvcBuilder.Services.AddFromConvertBinding(configure);
|
||||
|
||||
@@ -107,13 +107,13 @@ public static class AspNetCoreBuilderServiceCollectionExtensions
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configure"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<ConcurrentDictionary<Type, Type>> configure = default)
|
||||
public static IServiceCollection AddFromConvertBinding(this IServiceCollection services, Action<NonBlockingDictionary<Type, Type>> configure = default)
|
||||
{
|
||||
// 非 Web 环境跳过注册
|
||||
if (App.WebHostEnvironment == default) return services;
|
||||
|
||||
// 定义模型绑定转换器集合
|
||||
var modelBinderConverts = new ConcurrentDictionary<Type, Type>();
|
||||
var modelBinderConverts = new NonBlockingDictionary<Type, Type>();
|
||||
modelBinderConverts.TryAdd(typeof(DateTime), typeof(DateTimeModelConvertBinder));
|
||||
modelBinderConverts.TryAdd(typeof(DateTimeOffset), typeof(DateTimeOffsetModelConvertBinder));
|
||||
|
||||
|
@@ -27,13 +27,13 @@ public class FromConvertBinder : IModelBinder
|
||||
/// <summary>
|
||||
/// 定义模型绑定转换器集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts;
|
||||
private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="modelBinderConverts">定义模型绑定转换器集合</param>
|
||||
public FromConvertBinder(ConcurrentDictionary<Type, Type> modelBinderConverts)
|
||||
public FromConvertBinder(NonBlockingDictionary<Type, Type> modelBinderConverts)
|
||||
{
|
||||
_modelBinderConverts = modelBinderConverts;
|
||||
}
|
||||
|
@@ -28,13 +28,13 @@ public class FromConvertBinderProvider : IModelBinderProvider
|
||||
/// <summary>
|
||||
/// 定义模型绑定转换器集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<Type, Type> _modelBinderConverts;
|
||||
private readonly NonBlockingDictionary<Type, Type> _modelBinderConverts;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="modelBinderConverts">定义模型绑定转换器集合</param>
|
||||
public FromConvertBinderProvider(ConcurrentDictionary<Type, Type> modelBinderConverts)
|
||||
public FromConvertBinderProvider(NonBlockingDictionary<Type, Type> modelBinderConverts)
|
||||
{
|
||||
_modelBinderConverts = modelBinderConverts;
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ public static class DataValidator
|
||||
/// <summary>
|
||||
/// 验证类型正则表达式
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas;
|
||||
private static readonly NonBlockingDictionary<string, ValidationItemMetadataAttribute> ValidationItemMetadatas;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
@@ -57,7 +57,7 @@ public static class DataValidator
|
||||
ValidationItemMetadatas = GetValidationValidationItemMetadatas();
|
||||
|
||||
// 缓存所有正则表达式
|
||||
GetValidationTypeValidationItemMetadataCached = new ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)>();
|
||||
GetValidationTypeValidationItemMetadataCached = new NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -203,7 +203,7 @@ public static class DataValidator
|
||||
/// <summary>
|
||||
/// 获取验证类型验证Item集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached;
|
||||
private static readonly NonBlockingDictionary<object, (string, ValidationItemMetadataAttribute)> GetValidationTypeValidationItemMetadataCached;
|
||||
|
||||
/// <summary>
|
||||
/// 获取验证类型正则表达式(需要缓存)
|
||||
@@ -267,9 +267,9 @@ public static class DataValidator
|
||||
/// 获取验证类型所有有效的正则表达式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static ConcurrentDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas()
|
||||
private static NonBlockingDictionary<string, ValidationItemMetadataAttribute> GetValidationValidationItemMetadatas()
|
||||
{
|
||||
var vaidationItems = new ConcurrentDictionary<string, ValidationItemMetadataAttribute>();
|
||||
var vaidationItems = new NonBlockingDictionary<string, ValidationItemMetadataAttribute>();
|
||||
|
||||
// 查找所有 [ValidationMessageType] 类型中的 [ValidationMessage] 消息定义
|
||||
var customErrorMessages = ValidationMessageTypes.SelectMany(u => u.GetFields()
|
||||
|
@@ -353,7 +353,7 @@ public static class DependencyInjectionServiceCollectionExtensions
|
||||
/// <summary>
|
||||
/// 类型名称集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, Type> TypeNamedCollection;
|
||||
private static readonly NonBlockingDictionary<string, Type> TypeNamedCollection;
|
||||
|
||||
/// <summary>
|
||||
/// 创建代理方法
|
||||
@@ -374,7 +374,7 @@ public static class DependencyInjectionServiceCollectionExtensions
|
||||
GlobalServiceProxyType = App.EffectiveTypes
|
||||
.FirstOrDefault(u => typeof(AspectDispatchProxy).IsAssignableFrom(u) && typeof(IGlobalDispatchProxy).IsAssignableFrom(u) && u.IsClass && !u.IsInterface && !u.IsAbstract);
|
||||
|
||||
TypeNamedCollection = new ConcurrentDictionary<string, Type>();
|
||||
TypeNamedCollection = new NonBlockingDictionary<string, Type>();
|
||||
DispatchCreateMethod = typeof(AspectDispatchProxy).GetMethod(nameof(AspectDispatchProxy.Create));
|
||||
}
|
||||
}
|
||||
|
@@ -28,21 +28,21 @@ internal static class Penetrates
|
||||
/// <summary>
|
||||
/// 请求动词映射字典
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<string, string> VerbToHttpMethods { get; private set; }
|
||||
internal static NonBlockingDictionary<string, string> VerbToHttpMethods { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 控制器排序集合
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; }
|
||||
internal static NonBlockingDictionary<string, (string, int, Type)> ControllerOrderCollection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
static Penetrates()
|
||||
{
|
||||
ControllerOrderCollection = new ConcurrentDictionary<string, (string, int, Type)>();
|
||||
ControllerOrderCollection = new NonBlockingDictionary<string, (string, int, Type)>();
|
||||
|
||||
VerbToHttpMethods = new ConcurrentDictionary<string, string>
|
||||
VerbToHttpMethods = new NonBlockingDictionary<string, string>
|
||||
{
|
||||
["post"] = "POST",
|
||||
["add"] = "POST",
|
||||
@@ -67,13 +67,13 @@ internal static class Penetrates
|
||||
["patch"] = "PATCH"
|
||||
};
|
||||
|
||||
//IsApiControllerCached = new ConcurrentDictionary<Type, bool>();
|
||||
//IsApiControllerCached = new NonBlockingDictionary<Type, bool>();
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// <see cref="IsApiController(Type)"/> 缓存集合
|
||||
///// </summary>
|
||||
//private static readonly ConcurrentDictionary<Type, bool> IsApiControllerCached;
|
||||
//private static readonly NonBlockingDictionary<Type, bool> IsApiControllerCached;
|
||||
|
||||
/// <summary>
|
||||
/// 是否是Api控制器
|
||||
|
@@ -61,7 +61,7 @@ internal sealed class EventBusHostedService : BackgroundService
|
||||
/// <summary>
|
||||
/// 事件处理程序集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new();
|
||||
private readonly NonBlockingDictionary<EventHandlerWrapper, EventHandlerWrapper> _eventHandlers = new();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
@@ -295,7 +295,8 @@ internal sealed class EventBusHostedService : BackgroundService
|
||||
, retryAction: (total, times) =>
|
||||
{
|
||||
// 输出重试日志
|
||||
_logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId);
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true)
|
||||
_logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
|
@@ -31,7 +31,7 @@ public static class Oops
|
||||
/// <summary>
|
||||
/// 方法错误异常特性
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<MethodBase, MethodIfException> _errorMethods;
|
||||
private static readonly NonBlockingDictionary<MethodBase, MethodIfException> _errorMethods;
|
||||
|
||||
/// <summary>
|
||||
/// 错误代码类型
|
||||
@@ -41,7 +41,7 @@ public static class Oops
|
||||
/// <summary>
|
||||
/// 错误消息字典
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, string> _errorCodeMessages;
|
||||
private static readonly NonBlockingDictionary<string, string> _errorCodeMessages;
|
||||
|
||||
/// <summary>
|
||||
/// 友好异常设置
|
||||
@@ -53,7 +53,7 @@ public static class Oops
|
||||
/// </summary>
|
||||
static Oops()
|
||||
{
|
||||
_errorMethods = new ConcurrentDictionary<MethodBase, MethodIfException>();
|
||||
_errorMethods = new NonBlockingDictionary<MethodBase, MethodIfException>();
|
||||
_friendlyExceptionSettings = App.GetConfig<FriendlyExceptionSettingsOptions>("FriendlyExceptionSettings", true);
|
||||
_errorCodeTypes = GetErrorCodeTypes();
|
||||
_errorCodeMessages = GetErrorCodeMessages();
|
||||
@@ -258,9 +258,9 @@ public static class Oops
|
||||
/// 获取所有错误消息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static ConcurrentDictionary<string, string> GetErrorCodeMessages()
|
||||
private static NonBlockingDictionary<string, string> GetErrorCodeMessages()
|
||||
{
|
||||
var defaultErrorCodeMessages = new ConcurrentDictionary<string, string>();
|
||||
var defaultErrorCodeMessages = new NonBlockingDictionary<string, string>();
|
||||
|
||||
// 查找所有 [ErrorCodeType] 类型中的 [ErrorCodeMetadata] 元数据定义
|
||||
var errorCodeMessages = _errorCodeTypes.SelectMany(u => u.GetFields().Where(u => u.IsDefined(typeof(ErrorCodeItemMetadataAttribute))))
|
||||
|
@@ -23,7 +23,7 @@ public static class ILoggerExtensions
|
||||
/// 设置日志上下文
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
|
||||
/// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
|
||||
/// <returns></returns>
|
||||
public static IDisposable ScopeContext(this ILogger logger, IDictionary<string, object> properties)
|
||||
{
|
||||
|
@@ -82,7 +82,7 @@ public static class StringLoggingExtensions
|
||||
/// 配置日志上下文
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
|
||||
/// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
|
||||
/// <returns></returns>
|
||||
public static StringLoggingPart ScopeContext(this string message, IDictionary<string, object> properties)
|
||||
{
|
||||
|
@@ -44,7 +44,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope
|
||||
/// 记录日志所有滚动文件名
|
||||
/// </summary>
|
||||
/// <remarks>只有 MaxRollingFiles 和 FileSizeLimitBytes 大于 0 有效</remarks>
|
||||
internal readonly ConcurrentDictionary<string, FileInfo> _rollingFileNames = new();
|
||||
internal readonly NonBlockingDictionary<string, FileInfo> _rollingFileNames = new();
|
||||
|
||||
/// <summary>
|
||||
/// 文件日志写入器
|
||||
|
@@ -94,7 +94,7 @@ public sealed partial class StringLoggingPart
|
||||
/// <summary>
|
||||
/// 配置日志上下文
|
||||
/// </summary>
|
||||
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
|
||||
/// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
|
||||
/// <returns></returns>
|
||||
public StringLoggingPart ScopeContext(IDictionary<string, object> properties)
|
||||
{
|
||||
|
@@ -57,7 +57,7 @@ public static class Log
|
||||
/// <summary>
|
||||
/// 配置日志上下文
|
||||
/// </summary>
|
||||
/// <param name="properties">建议使用 ConcurrentDictionary 类型</param>
|
||||
/// <param name="properties">建议使用 NonBlockingDictionary 类型</param>
|
||||
/// <returns></returns>
|
||||
public static (ILogger logger, IDisposable scope) ScopeContext(IDictionary<string, object> properties)
|
||||
{
|
||||
|
@@ -21,7 +21,7 @@ internal sealed class JobCancellationToken : IJobCancellationToken
|
||||
/// <summary>
|
||||
/// 取消作业执行 Token 集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, CancellationTokenSource> _cancellationTokenSources;
|
||||
private readonly NonBlockingDictionary<string, CancellationTokenSource> _cancellationTokenSources;
|
||||
|
||||
/// <summary>
|
||||
/// 作业调度器日志服务
|
||||
|
@@ -167,7 +167,7 @@ public partial class JobDetail
|
||||
/// <summary>
|
||||
/// 带命名规则的数据库列名
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new();
|
||||
private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库列名
|
||||
|
@@ -65,7 +65,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
|
||||
/// <summary>
|
||||
/// 作业计划集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Scheduler> _schedulers = new();
|
||||
private readonly NonBlockingDictionary<string, Scheduler> _schedulers = new();
|
||||
|
||||
/// <summary>
|
||||
/// 作业计划构建器集合
|
||||
|
@@ -369,11 +369,13 @@ internal sealed class ScheduleHostedService : BackgroundService
|
||||
// 写入作业执行详细日志
|
||||
if (executionException == null)
|
||||
{
|
||||
jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext);
|
||||
if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true)
|
||||
jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext);
|
||||
if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error) == true)
|
||||
jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext);
|
||||
}
|
||||
|
||||
// 记录作业触发器运行信息
|
||||
|
@@ -380,7 +380,7 @@ public partial class Trigger
|
||||
/// <summary>
|
||||
/// 带命名规则的数据库列名
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<NamingConventions, string[]> _namingColumnNames = new();
|
||||
private readonly NonBlockingDictionary<NamingConventions, string[]> _namingColumnNames = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库列名
|
||||
|
@@ -83,11 +83,11 @@ public static class SpecificationDocumentBuilder
|
||||
|
||||
// 初始化常量
|
||||
_groupOrderRegex = new Regex(@"@(?<order>[0-9]+$)");
|
||||
GetActionGroupsCached = new ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>();
|
||||
GetControllerGroupsCached = new ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>>();
|
||||
GetGroupOpenApiInfoCached = new ConcurrentDictionary<string, SpecificationOpenApiInfo>();
|
||||
GetControllerTagCached = new ConcurrentDictionary<ControllerActionDescriptor, string>();
|
||||
GetActionTagCached = new ConcurrentDictionary<ApiDescription, string>();
|
||||
GetActionGroupsCached = new NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>>();
|
||||
GetControllerGroupsCached = new NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>>();
|
||||
GetGroupOpenApiInfoCached = new NonBlockingDictionary<string, SpecificationOpenApiInfo>();
|
||||
GetControllerTagCached = new NonBlockingDictionary<ControllerActionDescriptor, string>();
|
||||
GetActionTagCached = new NonBlockingDictionary<ApiDescription, string>();
|
||||
|
||||
// 默认分组,支持多个逗号分割
|
||||
DocumentGroupExtras = new List<GroupExtraInfo> { ResolveGroupExtraInfo(_specificationDocumentSettings.DefaultGroupName) };
|
||||
@@ -143,7 +143,7 @@ public static class SpecificationDocumentBuilder
|
||||
/// <summary>
|
||||
/// 获取分组信息缓存集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached;
|
||||
private static readonly NonBlockingDictionary<string, SpecificationOpenApiInfo> GetGroupOpenApiInfoCached;
|
||||
|
||||
/// <summary>
|
||||
/// 获取分组配置信息
|
||||
@@ -738,7 +738,7 @@ public static class SpecificationDocumentBuilder
|
||||
/// <summary>
|
||||
/// 获取控制器组缓存集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached;
|
||||
private static readonly NonBlockingDictionary<Type, IEnumerable<GroupExtraInfo>> GetControllerGroupsCached;
|
||||
|
||||
/// <summary>
|
||||
/// 获取控制器分组列表
|
||||
@@ -773,7 +773,7 @@ public static class SpecificationDocumentBuilder
|
||||
/// <summary>
|
||||
/// <see cref="GetActionGroups(MethodInfo)"/> 缓存集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached;
|
||||
private static readonly NonBlockingDictionary<MethodInfo, IEnumerable<GroupExtraInfo>> GetActionGroupsCached;
|
||||
|
||||
/// <summary>
|
||||
/// 获取动作方法分组列表
|
||||
@@ -808,7 +808,7 @@ public static class SpecificationDocumentBuilder
|
||||
/// <summary>
|
||||
/// <see cref="GetActionTag(ApiDescription)"/> 缓存集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<ControllerActionDescriptor, string> GetControllerTagCached;
|
||||
private static readonly NonBlockingDictionary<ControllerActionDescriptor, string> GetControllerTagCached;
|
||||
|
||||
/// <summary>
|
||||
/// 获取控制器标签
|
||||
@@ -835,7 +835,7 @@ public static class SpecificationDocumentBuilder
|
||||
/// <summary>
|
||||
/// <see cref="GetActionTag(ApiDescription)"/> 缓存集合
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<ApiDescription, string> GetActionTagCached;
|
||||
private static readonly NonBlockingDictionary<ApiDescription, string> GetActionTagCached;
|
||||
|
||||
/// <summary>
|
||||
/// 获取动作方法标签
|
||||
|
@@ -51,12 +51,12 @@ public static class UnifyContext
|
||||
/// <summary>
|
||||
/// 规范化结果提供器
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<string, UnifyMetadata> UnifyProviders = new();
|
||||
internal static NonBlockingDictionary<string, UnifyMetadata> UnifyProviders = new();
|
||||
|
||||
/// <summary>
|
||||
/// 规范化序列化配置
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<string, object> UnifySerializerSettings = new();
|
||||
internal static NonBlockingDictionary<string, object> UnifySerializerSettings = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取异常元数据
|
||||
|
@@ -14,7 +14,7 @@ using System.Collections.Concurrent;
|
||||
namespace ThingsGateway.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ConcurrentDictionary{TKey, TValue}" /> 拓展类
|
||||
/// <see cref="NonBlockingDictionary{TKey, TValue}" /> 拓展类
|
||||
/// </summary>
|
||||
internal static class ConcurrentDictionaryExtensions
|
||||
{
|
||||
@@ -24,7 +24,7 @@ internal static class ConcurrentDictionaryExtensions
|
||||
/// <typeparam name="TKey">字典键类型</typeparam>
|
||||
/// <typeparam name="TValue">字典值类型</typeparam>
|
||||
/// <param name="dictionary">
|
||||
/// <see cref="ConcurrentDictionary{TKey, TValue}" />
|
||||
/// <see cref="NonBlockingDictionary{TKey, TValue}" />
|
||||
/// </param>
|
||||
/// <param name="key">
|
||||
/// <typeparamref name="TKey" />
|
||||
@@ -36,7 +36,7 @@ internal static class ConcurrentDictionaryExtensions
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
internal static bool TryUpdate<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary
|
||||
internal static bool TryUpdate<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dictionary
|
||||
, TKey key
|
||||
, Func<TValue, TValue> updateFactory
|
||||
, out TValue? value)
|
||||
|
@@ -241,7 +241,7 @@ internal static class IDictionaryExtensions
|
||||
/// </summary>
|
||||
/// <remarks>其中键是由值通过给定的选择器函数生成的。</remarks>
|
||||
/// <param name="dictionary">
|
||||
/// <see cref="ConcurrentDictionary{TKey, TValue}" />
|
||||
/// <see cref="NonBlockingDictionary{TKey, TValue}" />
|
||||
/// </param>
|
||||
/// <param name="values">
|
||||
/// <see cref="IEnumerable{T}" />
|
||||
@@ -249,7 +249,7 @@ internal static class IDictionaryExtensions
|
||||
/// <param name="keySelector">键选择器</param>
|
||||
/// <typeparam name="TKey">字典键类型</typeparam>
|
||||
/// <typeparam name="TValue">字典值类型</typeparam>
|
||||
internal static void TryAdd<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary,
|
||||
internal static void TryAdd<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dictionary,
|
||||
IEnumerable<TValue>? values, Func<TValue, TKey> keySelector)
|
||||
where TKey : notnull
|
||||
{
|
||||
|
@@ -21,20 +21,20 @@ internal sealed class CoreOptions
|
||||
/// <summary>
|
||||
/// 已注册的组件元数据集合
|
||||
/// </summary>
|
||||
internal readonly ConcurrentDictionary<string, ComponentMetadata> _metadataOfRegistered;
|
||||
internal readonly NonBlockingDictionary<string, ComponentMetadata> _metadataOfRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// 子选项集合
|
||||
/// </summary>
|
||||
internal readonly ConcurrentDictionary<Type, object> _optionsInstances;
|
||||
internal readonly NonBlockingDictionary<Type, object> _optionsInstances;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="CoreOptions" />
|
||||
/// </summary>
|
||||
internal CoreOptions()
|
||||
{
|
||||
_optionsInstances = new ConcurrentDictionary<Type, object>();
|
||||
_metadataOfRegistered = new ConcurrentDictionary<string, ComponentMetadata>(StringComparer.OrdinalIgnoreCase);
|
||||
_optionsInstances = new NonBlockingDictionary<Type, object>();
|
||||
_metadataOfRegistered = new NonBlockingDictionary<string, ComponentMetadata>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
EntryComponentTypes = [];
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ public sealed class ObjectPropertyGetter<T> where T : class
|
||||
/// <summary>
|
||||
/// 对象类型实例属性值访问器集合
|
||||
/// </summary>
|
||||
internal readonly ConcurrentDictionary<string, Func<object, object?>> _propertyGetters = new();
|
||||
internal readonly NonBlockingDictionary<string, Func<object, object?>> _propertyGetters = new();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ObjectPropertyGetter{T}" />
|
||||
|
@@ -32,7 +32,7 @@ public sealed class ObjectPropertySetter<T> where T : class
|
||||
/// <summary>
|
||||
/// 对象类型实例属性值设置器集合
|
||||
/// </summary>
|
||||
internal readonly ConcurrentDictionary<string, Action<object, object?>> _propertySetters = new();
|
||||
internal readonly NonBlockingDictionary<string, Action<object, object?>> _propertySetters = new();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ObjectPropertySetter{T}" />
|
||||
|
@@ -27,7 +27,7 @@ public sealed class HttpDeclarativeBuilder
|
||||
/// <summary>
|
||||
/// HTTP 声明式 <see cref="IHttpDeclarativeExtractor" /> 提取器集合
|
||||
/// </summary>
|
||||
internal static readonly ConcurrentDictionary<Type, IHttpDeclarativeExtractor> _extractors = new([
|
||||
internal static readonly NonBlockingDictionary<Type, IHttpDeclarativeExtractor> _extractors = new([
|
||||
new(typeof(BaseAddressDeclarativeExtractor), new BaseAddressDeclarativeExtractor()),
|
||||
new(typeof(ValidationDeclarativeExtractor), new ValidationDeclarativeExtractor()),
|
||||
new(typeof(AutoSetHostHeaderDeclarativeExtractor), new AutoSetHostHeaderDeclarativeExtractor()),
|
||||
@@ -56,7 +56,7 @@ public sealed class HttpDeclarativeBuilder
|
||||
/// HTTP 声明式 <see cref="IHttpDeclarativeExtractor" /> 提取器集合(冻结)
|
||||
/// </summary>
|
||||
/// <remarks>该集合用于确保某些 HTTP 声明式提取器始终位于最后。</remarks>
|
||||
internal static readonly ConcurrentDictionary<Type, IFrozenHttpDeclarativeExtractor> _frozenExtractors = new([
|
||||
internal static readonly NonBlockingDictionary<Type, IFrozenHttpDeclarativeExtractor> _frozenExtractors = new([
|
||||
new(typeof(MultipartDeclarativeExtractor), new MultipartDeclarativeExtractor()),
|
||||
new(typeof(HttpMultipartFormDataBuilderDeclarativeExtractor),
|
||||
new HttpMultipartFormDataBuilderDeclarativeExtractor()),
|
||||
|
@@ -251,7 +251,8 @@ public sealed class ProfilerDelegatingHandler(ILogger<Logging> logger, IOptions<
|
||||
// 检查是否配置(注册)了日志程序
|
||||
if (remoteOptions.IsLoggingRegistered)
|
||||
{
|
||||
logger.Log(remoteOptions.ProfilerLogLevel, "{message}", message);
|
||||
if (logger?.IsEnabled(remoteOptions.ProfilerLogLevel) == true)
|
||||
logger.Log(remoteOptions.ProfilerLogLevel, "{message}", message);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -27,7 +27,7 @@ public class MessagePackContentProcessor : HttpContentProcessorBase
|
||||
/// <summary>
|
||||
/// MessagePack 序列化器委托字典缓存
|
||||
/// </summary>
|
||||
internal static readonly ConcurrentDictionary<Type, Func<object, byte[]>> _serializerCache = new();
|
||||
internal static readonly NonBlockingDictionary<Type, Func<object, byte[]>> _serializerCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 MessagePack 序列化器委托
|
||||
|
@@ -23,7 +23,7 @@ public class MemoryCache : Cache
|
||||
{
|
||||
#region 属性
|
||||
/// <summary>缓存核心</summary>
|
||||
protected ConcurrentDictionary<String, CacheItem> _cache = new();
|
||||
protected NonBlockingDictionary<String, CacheItem> _cache = new();
|
||||
|
||||
/// <summary>容量。容量超标时,采用LRU机制删除,默认100_000</summary>
|
||||
public Int32 Capacity { get; set; } = 100_000;
|
||||
@@ -379,7 +379,7 @@ public class MemoryCache : Cache
|
||||
/// <returns></returns>
|
||||
public override IDictionary<String, T> GetDictionary<T>(String key)
|
||||
{
|
||||
var item = GetOrAddItem(key, k => new ConcurrentDictionary<String, T>());
|
||||
var item = GetOrAddItem(key, k => new NonBlockingDictionary<String, T>());
|
||||
return item.Visit<IDictionary<String, T>>() ??
|
||||
throw new InvalidCastException($"Unable to convert the value of [{key}] from {item.TypeCode} to {typeof(IDictionary<String, T>)}");
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
后续例程与使用说明均以Redis为例,各缓存实现类似。
|
||||
|
||||
### 内存缓存 MemoryCache
|
||||
MemoryCache核心是并发字典ConcurrentDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能。
|
||||
MemoryCache核心是并发字典NonBlockingDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能。
|
||||
MemoryCache支持过期时间,默认容量10万个,未过期key超过该值后,每60秒根据LRU清理溢出部分。
|
||||
常用于进程内千万级以下数据缓存场景。
|
||||
|
||||
|
@@ -44,7 +44,7 @@ public static class CollectionHelper
|
||||
{
|
||||
//if (collection == null) return null;
|
||||
|
||||
if (collection is ConcurrentDictionary<TKey, TValue> cdiv && cdiv.Keys is IList<TKey> list) return list;
|
||||
if (collection is NonBlockingDictionary<TKey, TValue> cdiv && cdiv.Keys is IList<TKey> list) return list;
|
||||
|
||||
if (collection.Count == 0) return [];
|
||||
lock (collection)
|
||||
@@ -65,8 +65,8 @@ public static class CollectionHelper
|
||||
{
|
||||
//if (collection == null) return null;
|
||||
|
||||
//if (collection is ConcurrentDictionary<TKey, TValue> cdiv) return cdiv.Values as IList<TValue>;
|
||||
if (collection is ConcurrentDictionary<TKey, TValue> cdiv && cdiv.Values is IList<TValue> list) return list;
|
||||
//if (collection is NonBlockingDictionary<TKey, TValue> cdiv) return cdiv.Values as IList<TValue>;
|
||||
if (collection is NonBlockingDictionary<TKey, TValue> cdiv && cdiv.Values is IList<TValue> list) return list;
|
||||
|
||||
if (collection.Count == 0) return [];
|
||||
lock (collection)
|
||||
|
@@ -9,7 +9,7 @@ namespace ThingsGateway.NewLife.Collections;
|
||||
/// </remarks>
|
||||
public class ConcurrentHashSet<T> : IEnumerable<T> where T : notnull
|
||||
{
|
||||
private readonly ConcurrentDictionary<T, Byte> _dic = new();
|
||||
private readonly NonBlockingDictionary<T, Byte> _dic = new();
|
||||
|
||||
/// <summary>是否空集合</summary>
|
||||
public Boolean IsEmpty => _dic.IsEmpty;
|
||||
|
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal abstract partial class DictionaryImpl<TKey, TKeyStore, TValue>
|
||||
: DictionaryImpl<TKey, TValue>
|
||||
{
|
||||
internal override Snapshot GetSnapshot()
|
||||
{
|
||||
return new SnapshotImpl(this);
|
||||
}
|
||||
|
||||
private class SnapshotImpl : Snapshot
|
||||
{
|
||||
private readonly DictionaryImpl<TKey, TKeyStore, TValue> _table;
|
||||
|
||||
public SnapshotImpl(DictionaryImpl<TKey, TKeyStore, TValue> dict)
|
||||
{
|
||||
this._table = dict;
|
||||
|
||||
// linearization point.
|
||||
// if table is quiescent and has no copy in progress,
|
||||
// we can simply iterate over its table.
|
||||
while (_table._newTable != null)
|
||||
{
|
||||
// there is a copy in progress, finish it and try again
|
||||
_table.HelpCopy(copy_all: true);
|
||||
this._table = (DictionaryImpl<TKey, TKeyStore, TValue>)this._table._topDict._table;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Count => _table.Count;
|
||||
|
||||
public override bool MoveNext()
|
||||
{
|
||||
var entries = this._table._entries;
|
||||
while (_idx < entries.Length)
|
||||
{
|
||||
var nextEntry = entries[_idx++];
|
||||
|
||||
if (nextEntry.value != null)
|
||||
{
|
||||
var nextKstore = nextEntry.key;
|
||||
if (nextKstore == null)
|
||||
{
|
||||
// slot was deleted.
|
||||
continue;
|
||||
}
|
||||
|
||||
_curKey = _table.keyFromEntry(nextKstore);
|
||||
object nextV = _table.TryGetValue(_curKey);
|
||||
if (nextV != null)
|
||||
{
|
||||
_curValue = _table.FromObjectValue(nextV);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_curKey = default;
|
||||
_curValue = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_idx = 0;
|
||||
_curKey = default;
|
||||
_curValue = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal abstract class DictionaryImpl
|
||||
{
|
||||
internal enum ValueMatch
|
||||
{
|
||||
Any, // sets new value unconditionally, used by index set and TryRemove(key)
|
||||
NullOrDead, // set value if original value is null or dead, used by Add/TryAdd
|
||||
NotNullOrDead, // set value if original value is alive, used by Remove
|
||||
OldValue, // sets new value if old value matches
|
||||
}
|
||||
|
||||
internal sealed class Prime
|
||||
{
|
||||
internal object originalValue;
|
||||
|
||||
public Prime(object originalValue)
|
||||
{
|
||||
this.originalValue = originalValue;
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly object TOMBSTONE = new object();
|
||||
internal static readonly Prime TOMBPRIME = new Prime(TOMBSTONE);
|
||||
internal static readonly object NULLVALUE = new object();
|
||||
|
||||
// represents a trivially copied empty entry
|
||||
// we insert it in the old table during rehashing
|
||||
// to reduce chances that more entries are added
|
||||
protected const int TOMBPRIMEHASH = 1 << 31;
|
||||
|
||||
// we cannot distigush zero keys from uninitialized state
|
||||
// so we force them to have this special hash instead
|
||||
protected const int ZEROHASH = 1 << 30;
|
||||
|
||||
// all regular hashes have both these bits set
|
||||
// to be different from either 0, TOMBPRIMEHASH or ZEROHASH
|
||||
// having only these bits set in a case of Ref key means that the slot is permanently deleted.
|
||||
protected const int SPECIAL_HASH_BITS = TOMBPRIMEHASH | ZEROHASH;
|
||||
|
||||
// Heuristic to decide if we have reprobed toooo many times. Running over
|
||||
// the reprobe limit on a 'get' call acts as a 'miss'; on a 'put' call it
|
||||
// can trigger a table resize. Several places must have exact agreement on
|
||||
// what the reprobe_limit is, so we share it here.
|
||||
protected const int REPROBE_LIMIT = 4;
|
||||
protected const int REPROBE_LIMIT_SHIFT = 8;
|
||||
|
||||
protected static int ReprobeLimit(int lenMask)
|
||||
{
|
||||
// 1/2 of table with some extra
|
||||
return REPROBE_LIMIT + (lenMask >> REPROBE_LIMIT_SHIFT);
|
||||
}
|
||||
|
||||
protected static bool EntryValueNullOrDead(object entryValue)
|
||||
{
|
||||
return entryValue == null || entryValue == TOMBSTONE;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static int ReduceHashToIndex(int fullHash, int lenMask)
|
||||
{
|
||||
var h = (uint)fullHash;
|
||||
|
||||
// xor-shift some upper bits down, in case if variations are mostly in high bits
|
||||
// and scatter the bits a little to break up clusters if hashes are periodic (like 42, 43, 44, ...)
|
||||
// long clusters can cause long reprobes. small clusters are ok though.
|
||||
h ^= h >> 15;
|
||||
h ^= h >> 8;
|
||||
h += (h >> 3) * 2654435769u;
|
||||
|
||||
return (int)h & lenMask;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static object ToObjectValue<TValue>(TValue value)
|
||||
{
|
||||
if (default(TValue) != null)
|
||||
{
|
||||
return new Boxed<TValue>(value);
|
||||
}
|
||||
|
||||
return (object)value ?? NULLVALUE;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal sealed class DictionaryImplBoxed<TKey, TValue>
|
||||
: DictionaryImpl<TKey, Boxed<TKey>, TValue>
|
||||
{
|
||||
internal DictionaryImplBoxed(int capacity, NonBlockingDictionary<TKey, TValue> topDict)
|
||||
: base(capacity, topDict)
|
||||
{
|
||||
}
|
||||
|
||||
internal DictionaryImplBoxed(int capacity, DictionaryImplBoxed<TKey, TValue> other)
|
||||
: base(capacity, other)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForPut(ref Boxed<TKey> entryKey, TKey key)
|
||||
{
|
||||
var entryKeyValue = entryKey;
|
||||
if (entryKeyValue == null)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref entryKey, new Boxed<TKey>(key), null);
|
||||
if (entryKeyValue == null)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return _keyComparer.Equals(key, entryKeyValue.Value);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForCopy(ref Boxed<TKey> entryKey, Boxed<TKey> key)
|
||||
{
|
||||
var entryKeyValue = entryKey;
|
||||
if (entryKeyValue == null)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, null);
|
||||
if (entryKeyValue == null)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return _keyComparer.Equals(key.Value, entryKeyValue.Value);
|
||||
}
|
||||
|
||||
protected override bool keyEqual(TKey key, Boxed<TKey> entryKey)
|
||||
{
|
||||
//NOTE: slots are claimed in two stages - claim a hash, then set a key
|
||||
// it is possible to observe a slot with a null key, but with hash already set
|
||||
// that is not a match since the key is not yet in the table
|
||||
if (entryKey == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _keyComparer.Equals(key, entryKey.Value);
|
||||
}
|
||||
|
||||
protected override DictionaryImpl<TKey, Boxed<TKey>, TValue> CreateNew(int capacity)
|
||||
{
|
||||
return new DictionaryImplBoxed<TKey, TValue>(capacity, this);
|
||||
}
|
||||
|
||||
protected override TKey keyFromEntry(Boxed<TKey> entryKey)
|
||||
{
|
||||
return entryKey.Value;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
|
||||
internal class Boxed<T>
|
||||
{
|
||||
// 0 - allow writes, 1 - someone is writing, 2 frozen.
|
||||
public int writeStatus;
|
||||
public T Value;
|
||||
|
||||
public Boxed(T key)
|
||||
{
|
||||
this.Value = key;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(this.Value, Unsafe.As<Boxed<T>>(obj).Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryVolatileWrite(T value)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref writeStatus, 1, 0) == 0)
|
||||
{
|
||||
Value = value;
|
||||
Volatile.Write(ref writeStatus, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryCompareExchange(T oldValue, T newValue, out bool changed)
|
||||
{
|
||||
changed = false;
|
||||
if (Interlocked.CompareExchange(ref writeStatus, 1, 0) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EqualityComparer<T>.Default.Equals(Value, oldValue))
|
||||
{
|
||||
Value = newValue;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
Volatile.Write(ref writeStatus, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Freeze()
|
||||
{
|
||||
// Wait for writers (1) to leave. Already 2 is ok, or set 0 -> 2.
|
||||
while (Interlocked.CompareExchange(ref writeStatus, 2, 0) == 1) ;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal sealed class DictionaryImplInt<TValue>
|
||||
: DictionaryImpl<int, int, TValue>
|
||||
{
|
||||
internal DictionaryImplInt(int capacity, NonBlockingDictionary<int, TValue> topDict)
|
||||
: base(capacity, topDict)
|
||||
{
|
||||
}
|
||||
|
||||
internal DictionaryImplInt(int capacity, DictionaryImplInt<TValue> other)
|
||||
: base(capacity, other)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForPut(ref int entryKey, int key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForCopy(ref int entryKey, int key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
private bool TryClaimSlot(ref int entryKey, int key)
|
||||
{
|
||||
var entryKeyValue = entryKey;
|
||||
//zero keys are claimed via hash
|
||||
if (entryKeyValue == 0 & key != 0)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0);
|
||||
if (entryKeyValue == 0)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue);
|
||||
}
|
||||
|
||||
protected override int hash(int key)
|
||||
{
|
||||
if (key == 0)
|
||||
{
|
||||
return ZEROHASH;
|
||||
}
|
||||
|
||||
return base.hash(key);
|
||||
}
|
||||
|
||||
protected override bool keyEqual(int key, int entryKey)
|
||||
{
|
||||
return key == entryKey || _keyComparer.Equals(key, entryKey);
|
||||
}
|
||||
|
||||
protected override DictionaryImpl<int, int, TValue> CreateNew(int capacity)
|
||||
{
|
||||
return new DictionaryImplInt<TValue>(capacity, this);
|
||||
}
|
||||
|
||||
protected override int keyFromEntry(int entryKey)
|
||||
{
|
||||
return entryKey;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DictionaryImplIntNoComparer<TValue>
|
||||
: DictionaryImpl<int, int, TValue>
|
||||
{
|
||||
internal DictionaryImplIntNoComparer(int capacity, NonBlockingDictionary<int, TValue> topDict)
|
||||
: base(capacity, topDict)
|
||||
{
|
||||
}
|
||||
|
||||
internal DictionaryImplIntNoComparer(int capacity, DictionaryImplIntNoComparer<TValue> other)
|
||||
: base(capacity, other)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForPut(ref int entryKey, int key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForCopy(ref int entryKey, int key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
private bool TryClaimSlot(ref int entryKey, int key)
|
||||
{
|
||||
var entryKeyValue = entryKey;
|
||||
//zero keys are claimed via hash
|
||||
if (entryKeyValue == 0 & key != 0)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0);
|
||||
if (entryKeyValue == 0)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return key == entryKeyValue;
|
||||
}
|
||||
|
||||
// inline the base implementation to devirtualize calls to hash and keyEqual
|
||||
internal override object TryGetValue(int key)
|
||||
{
|
||||
return base.TryGetValue(key);
|
||||
}
|
||||
|
||||
protected override int hash(int key)
|
||||
{
|
||||
return (key == 0) ?
|
||||
ZEROHASH :
|
||||
key | SPECIAL_HASH_BITS;
|
||||
}
|
||||
|
||||
protected override bool keyEqual(int key, int entryKey)
|
||||
{
|
||||
return key == entryKey;
|
||||
}
|
||||
|
||||
protected override DictionaryImpl<int, int, TValue> CreateNew(int capacity)
|
||||
{
|
||||
return new DictionaryImplIntNoComparer<TValue>(capacity, this);
|
||||
}
|
||||
|
||||
protected override int keyFromEntry(int entryKey)
|
||||
{
|
||||
return entryKey;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal sealed class DictionaryImplLong<TValue>
|
||||
: DictionaryImpl<long, long, TValue>
|
||||
{
|
||||
internal DictionaryImplLong(int capacity, NonBlockingDictionary<long, TValue> topDict)
|
||||
: base(capacity, topDict)
|
||||
{
|
||||
}
|
||||
|
||||
internal DictionaryImplLong(int capacity, DictionaryImplLong<TValue> other)
|
||||
: base(capacity, other)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForPut(ref long entryKey, long key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForCopy(ref long entryKey, long key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
private bool TryClaimSlot(ref long entryKey, long key)
|
||||
{
|
||||
var entryKeyValue = entryKey;
|
||||
//zero keys are claimed via hash
|
||||
if (entryKeyValue == 0 && key != 0)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0);
|
||||
if (entryKeyValue == 0)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue);
|
||||
}
|
||||
|
||||
protected override int hash(long key)
|
||||
{
|
||||
if (key == 0)
|
||||
{
|
||||
return ZEROHASH;
|
||||
}
|
||||
|
||||
return base.hash(key);
|
||||
}
|
||||
|
||||
protected override bool keyEqual(long key, long entryKey)
|
||||
{
|
||||
return key == entryKey || _keyComparer.Equals(key, entryKey);
|
||||
}
|
||||
|
||||
protected override DictionaryImpl<long, long, TValue> CreateNew(int capacity)
|
||||
{
|
||||
return new DictionaryImplLong<TValue>(capacity, this);
|
||||
}
|
||||
|
||||
protected override long keyFromEntry(long entryKey)
|
||||
{
|
||||
return entryKey;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DictionaryImplLongNoComparer<TValue>
|
||||
: DictionaryImpl<long, long, TValue>
|
||||
{
|
||||
internal DictionaryImplLongNoComparer(int capacity, NonBlockingDictionary<long, TValue> topDict)
|
||||
: base(capacity, topDict)
|
||||
{
|
||||
}
|
||||
|
||||
internal DictionaryImplLongNoComparer(int capacity, DictionaryImplLongNoComparer<TValue> other)
|
||||
: base(capacity, other)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForPut(ref long entryKey, long key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForCopy(ref long entryKey, long key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
private bool TryClaimSlot(ref long entryKey, long key)
|
||||
{
|
||||
var entryKeyValue = entryKey;
|
||||
//zero keys are claimed via hash
|
||||
if (entryKeyValue == 0 && key != 0)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, 0);
|
||||
if (entryKeyValue == 0)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return key == entryKeyValue;
|
||||
}
|
||||
|
||||
// inline the base implementation to devirtualize calls to hash and keyEqual
|
||||
internal override object TryGetValue(long key)
|
||||
{
|
||||
return base.TryGetValue(key);
|
||||
}
|
||||
|
||||
protected override int hash(long key)
|
||||
{
|
||||
return (key == 0) ?
|
||||
ZEROHASH :
|
||||
key.GetHashCode() | SPECIAL_HASH_BITS;
|
||||
}
|
||||
|
||||
protected override bool keyEqual(long key, long entryKey)
|
||||
{
|
||||
return key == entryKey;
|
||||
}
|
||||
|
||||
protected override DictionaryImpl<long, long, TValue> CreateNew(int capacity)
|
||||
{
|
||||
return new DictionaryImplLongNoComparer<TValue>(capacity, this);
|
||||
}
|
||||
|
||||
protected override long keyFromEntry(long entryKey)
|
||||
{
|
||||
return entryKey;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal sealed class DictionaryImplNint<TValue>
|
||||
: DictionaryImpl<nint, nint, TValue>
|
||||
{
|
||||
internal DictionaryImplNint(int capacity, NonBlockingDictionary<nint, TValue> topDict)
|
||||
: base(capacity, topDict)
|
||||
{
|
||||
}
|
||||
|
||||
internal DictionaryImplNint(int capacity, DictionaryImplNint<TValue> other)
|
||||
: base(capacity, other)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForPut(ref nint entryKey, nint key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForCopy(ref nint entryKey, nint key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
private bool TryClaimSlot(ref nint entryKey, nint key)
|
||||
{
|
||||
var entryKeyValue = entryKey;
|
||||
//zero keys are claimed via hash
|
||||
if (entryKeyValue == 0 && key != 0)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, (nint)0);
|
||||
if (entryKeyValue == 0)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return key == entryKeyValue || _keyComparer.Equals(key, entryKeyValue);
|
||||
}
|
||||
|
||||
protected override int hash(nint key)
|
||||
{
|
||||
if (key == 0)
|
||||
{
|
||||
return ZEROHASH;
|
||||
}
|
||||
|
||||
return base.hash(key);
|
||||
}
|
||||
|
||||
protected override bool keyEqual(nint key, nint entryKey)
|
||||
{
|
||||
return key == entryKey || _keyComparer.Equals(key, entryKey);
|
||||
}
|
||||
|
||||
protected override DictionaryImpl<nint, nint, TValue> CreateNew(int capacity)
|
||||
{
|
||||
return new DictionaryImplNint<TValue>(capacity, this);
|
||||
}
|
||||
|
||||
protected override nint keyFromEntry(nint entryKey)
|
||||
{
|
||||
return entryKey;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DictionaryImplNintNoComparer<TValue>
|
||||
: DictionaryImpl<nint, nint, TValue>
|
||||
{
|
||||
internal DictionaryImplNintNoComparer(int capacity, NonBlockingDictionary<nint, TValue> topDict)
|
||||
: base(capacity, topDict)
|
||||
{
|
||||
}
|
||||
|
||||
internal DictionaryImplNintNoComparer(int capacity, DictionaryImplNintNoComparer<TValue> other)
|
||||
: base(capacity, other)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForPut(ref nint entryKey, nint key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForCopy(ref nint entryKey, nint key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
private bool TryClaimSlot(ref nint entryKey, nint key)
|
||||
{
|
||||
var entryKeyValue = entryKey;
|
||||
//zero keys are claimed via hash
|
||||
if (entryKeyValue == 0 && key != 0)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref entryKey, key, (nint)0);
|
||||
if (entryKeyValue == 0)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return key == entryKeyValue;
|
||||
}
|
||||
|
||||
// inline the base implementation to devirtualize calls to hash and keyEqual
|
||||
internal override object TryGetValue(nint key)
|
||||
{
|
||||
return base.TryGetValue(key);
|
||||
}
|
||||
|
||||
protected override int hash(nint key)
|
||||
{
|
||||
return (key == 0) ?
|
||||
ZEROHASH :
|
||||
key.GetHashCode() | SPECIAL_HASH_BITS;
|
||||
}
|
||||
|
||||
protected override bool keyEqual(nint key, nint entryKey)
|
||||
{
|
||||
return key == entryKey;
|
||||
}
|
||||
|
||||
protected override DictionaryImpl<nint, nint, TValue> CreateNew(int capacity)
|
||||
{
|
||||
return new DictionaryImplNintNoComparer<TValue>(capacity, this);
|
||||
}
|
||||
|
||||
protected override nint keyFromEntry(nint entryKey)
|
||||
{
|
||||
return entryKey;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal sealed class DictionaryImplRef<TKey, TKeyStore, TValue>
|
||||
: DictionaryImpl<TKey, TKey, TValue>
|
||||
{
|
||||
internal DictionaryImplRef(int capacity, NonBlockingDictionary<TKey, TValue> topDict)
|
||||
: base(capacity, topDict)
|
||||
{
|
||||
Debug.Assert(!typeof(TKey).IsValueType);
|
||||
}
|
||||
|
||||
internal DictionaryImplRef(int capacity, DictionaryImplRef<TKey, TKeyStore, TValue> other)
|
||||
: base(capacity, other)
|
||||
{
|
||||
Debug.Assert(!typeof(TKey).IsValueType);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForPut(ref TKey entryKey, TKey key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
protected override bool TryClaimSlotForCopy(ref TKey entryKey, TKey key)
|
||||
{
|
||||
return TryClaimSlot(ref entryKey, key);
|
||||
}
|
||||
|
||||
private bool TryClaimSlot(ref TKey entryKey, TKey key)
|
||||
{
|
||||
ref object keyLocation = ref Unsafe.As<TKey, object>(ref entryKey);
|
||||
object entryKeyValue = keyLocation;
|
||||
if (entryKeyValue == null)
|
||||
{
|
||||
entryKeyValue = Interlocked.CompareExchange(ref keyLocation, key, null);
|
||||
if (entryKeyValue == null)
|
||||
{
|
||||
// claimed a new slot
|
||||
this.allocatedSlotCount.Increment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (object)key == entryKeyValue ||
|
||||
_keyComparer.Equals(key, Unsafe.As<object, TKey>(ref entryKeyValue));
|
||||
}
|
||||
|
||||
// inline the base implementation to devirtualize calls to hash and keyEqual
|
||||
internal override object TryGetValue(TKey key)
|
||||
{
|
||||
return base.TryGetValue(key);
|
||||
}
|
||||
|
||||
protected override int hash(TKey key)
|
||||
{
|
||||
return base.hash(key);
|
||||
}
|
||||
|
||||
protected override bool keyEqual(TKey key, TKey entryKey)
|
||||
{
|
||||
if ((object)key == (object)entryKey)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//NOTE: slots are claimed in two stages - claim a hash, then set a key
|
||||
// it is possible to observe a slot with a null key, but with hash already set
|
||||
// that is not a match since the key is not yet in the table
|
||||
if (entryKey == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _keyComparer.Equals(entryKey, key);
|
||||
}
|
||||
|
||||
protected override DictionaryImpl<TKey, TKey, TValue> CreateNew(int capacity)
|
||||
{
|
||||
return new DictionaryImplRef<TKey, TKeyStore, TValue>(capacity, this);
|
||||
}
|
||||
|
||||
protected override TKey keyFromEntry(TKey entryKey)
|
||||
{
|
||||
return entryKey;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal abstract class DictionaryImpl<TKey, TValue>
|
||||
: DictionaryImpl
|
||||
{
|
||||
private readonly bool _valueIsValueType = typeof(TValue).IsValueType;
|
||||
|
||||
internal IEqualityComparer<TKey> _keyComparer;
|
||||
|
||||
internal abstract void Clear();
|
||||
internal abstract int Count { get; }
|
||||
|
||||
internal abstract object TryGetValue(TKey key);
|
||||
internal abstract bool PutIfMatch(TKey key, TValue newVal, ref TValue oldValue, ValueMatch match);
|
||||
internal abstract bool RemoveIfMatch(TKey key, ref TValue oldValue, ValueMatch match);
|
||||
internal abstract TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory);
|
||||
|
||||
internal abstract Snapshot GetSnapshot();
|
||||
|
||||
internal abstract class Snapshot
|
||||
{
|
||||
protected int _idx;
|
||||
protected TKey _curKey;
|
||||
protected TValue _curValue;
|
||||
|
||||
public abstract int Count { get; }
|
||||
public abstract bool MoveNext();
|
||||
public abstract void Reset();
|
||||
|
||||
internal DictionaryEntry Entry
|
||||
{
|
||||
get
|
||||
{
|
||||
return new DictionaryEntry(_curKey, _curValue);
|
||||
}
|
||||
}
|
||||
|
||||
internal KeyValuePair<TKey, TValue> Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return new KeyValuePair<TKey, TValue>(this._curKey, _curValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected TValue FromObjectValue(object obj)
|
||||
{
|
||||
// regular value type
|
||||
if (default(TValue) != null)
|
||||
{
|
||||
return Unsafe.As<Boxed<TValue>>(obj).Value;
|
||||
}
|
||||
|
||||
// null
|
||||
if (obj == NULLVALUE)
|
||||
{
|
||||
return default(TValue);
|
||||
}
|
||||
|
||||
// ref type
|
||||
if (!_valueIsValueType)
|
||||
{
|
||||
return Unsafe.As<object, TValue>(ref obj);
|
||||
}
|
||||
|
||||
// nullable
|
||||
return (TValue)obj;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,204 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
/// <summary>
|
||||
/// Scalable 32bit counter that can be used from multiple threads.
|
||||
/// </summary>
|
||||
public sealed class Counter32 : CounterBase
|
||||
{
|
||||
private class Cell
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = CACHE_LINE * 2 - OBJ_HEADER_SIZE)]
|
||||
public struct SpacedCounter
|
||||
{
|
||||
[FieldOffset(CACHE_LINE - OBJ_HEADER_SIZE)]
|
||||
public int count;
|
||||
}
|
||||
|
||||
public SpacedCounter counter;
|
||||
}
|
||||
|
||||
// spaced out counters
|
||||
private Cell[]? cells;
|
||||
|
||||
// default counter
|
||||
private int count;
|
||||
|
||||
// delayed estimated count
|
||||
private int lastCount;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the counter at the time of the call.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value may miss in-progress updates if the counter is being concurrently modified.
|
||||
/// </remarks>
|
||||
public int Value
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = this.count;
|
||||
var cells = this.cells;
|
||||
|
||||
if (cells != null)
|
||||
{
|
||||
for (int i = 0; i < cells.Length; i++)
|
||||
{
|
||||
var cell = cells[i];
|
||||
if (cell != null)
|
||||
{
|
||||
count += cell.counter.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the approximate value of the counter at the time of the call.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// EstimatedValue could be significantly cheaper to obtain, but may be slightly delayed.
|
||||
/// </remarks>
|
||||
public int EstimatedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.cells == null)
|
||||
{
|
||||
return this.count;
|
||||
}
|
||||
|
||||
var curTicks = (uint)Environment.TickCount;
|
||||
// more than a millisecond passed?
|
||||
if (curTicks != lastCountTicks)
|
||||
{
|
||||
lastCountTicks = curTicks;
|
||||
lastCount = Value;
|
||||
}
|
||||
|
||||
return lastCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the counter by 1.
|
||||
/// </summary>
|
||||
public void Increment()
|
||||
{
|
||||
int curCellCount = this.cellCount;
|
||||
var drift = increment(ref GetCountRef(curCellCount));
|
||||
|
||||
if (drift != 0)
|
||||
{
|
||||
TryAddCell(curCellCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the counter by 1.
|
||||
/// </summary>
|
||||
public void Decrement()
|
||||
{
|
||||
int curCellCount = this.cellCount;
|
||||
var drift = decrement(ref GetCountRef(curCellCount));
|
||||
|
||||
if (drift != 0)
|
||||
{
|
||||
TryAddCell(curCellCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the counter by 'value'.
|
||||
/// </summary>
|
||||
public void Add(int value)
|
||||
{
|
||||
int curCellCount = this.cellCount;
|
||||
var drift = add(ref GetCountRef(curCellCount), value);
|
||||
|
||||
if (drift != 0)
|
||||
{
|
||||
TryAddCell(curCellCount);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ref int GetCountRef(int curCellCount)
|
||||
{
|
||||
ref var countRef = ref count;
|
||||
|
||||
Cell[]? cells;
|
||||
if ((cells = this.cells) != null && curCellCount > 1)
|
||||
{
|
||||
var cell = cells[GetIndex((uint)curCellCount)];
|
||||
if (cell != null)
|
||||
{
|
||||
countRef = ref cell.counter.count;
|
||||
}
|
||||
}
|
||||
|
||||
return ref countRef;
|
||||
}
|
||||
|
||||
private static int increment(ref int val)
|
||||
{
|
||||
return -val - 1 + Interlocked.Increment(ref val);
|
||||
}
|
||||
|
||||
private static int add(ref int val, int inc)
|
||||
{
|
||||
return -val - inc + Interlocked.Add(ref val, inc);
|
||||
}
|
||||
|
||||
private static int decrement(ref int val)
|
||||
{
|
||||
return val - 1 - Interlocked.Decrement(ref val);
|
||||
}
|
||||
|
||||
private void TryAddCell(int curCellCount)
|
||||
{
|
||||
if (curCellCount < s_MaxCellCount)
|
||||
{
|
||||
TryAddCellCore(curCellCount);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void TryAddCellCore(int curCellCount)
|
||||
{
|
||||
var cells = this.cells;
|
||||
if (cells == null)
|
||||
{
|
||||
var newCells = new Cell[s_MaxCellCount];
|
||||
cells = Interlocked.CompareExchange(ref this.cells, newCells, null) ?? newCells;
|
||||
}
|
||||
|
||||
if (cells[curCellCount] == null)
|
||||
{
|
||||
Interlocked.CompareExchange(ref cells[curCellCount], new Cell(), null);
|
||||
}
|
||||
|
||||
if (this.cellCount == curCellCount)
|
||||
{
|
||||
Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount);
|
||||
//if (Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount) == curCellCount)
|
||||
//{
|
||||
// System.Console.WriteLine(curCellCount + 1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
/// <summary>
|
||||
/// Scalable 64bit counter that can be used from multiple threads.
|
||||
/// </summary>
|
||||
public sealed class Counter64 : CounterBase
|
||||
{
|
||||
private class Cell
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = CACHE_LINE * 2 - OBJ_HEADER_SIZE)]
|
||||
public struct SpacedCounter
|
||||
{
|
||||
[FieldOffset(CACHE_LINE - OBJ_HEADER_SIZE)]
|
||||
public long count;
|
||||
}
|
||||
|
||||
public SpacedCounter counter;
|
||||
}
|
||||
|
||||
// spaced out counters
|
||||
private Cell[]? cells;
|
||||
|
||||
// default counter
|
||||
private long count;
|
||||
|
||||
// delayed count
|
||||
private long lastCount;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the counter at the time of the call.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value may miss in-progress updates if the counter is being concurrently modified.
|
||||
/// </remarks>
|
||||
public long Value
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = this.count;
|
||||
var cells = this.cells;
|
||||
|
||||
if (cells != null)
|
||||
{
|
||||
for (int i = 0; i < cells.Length; i++)
|
||||
{
|
||||
var cell = cells[i];
|
||||
if (cell != null)
|
||||
{
|
||||
count += cell.counter.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the approximate value of the counter at the time of the call.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// EstimatedValue could be significantly cheaper to obtain, but may be slightly delayed.
|
||||
/// </remarks>
|
||||
public long EstimatedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.cellCount == 0)
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
var curTicks = (uint)Environment.TickCount;
|
||||
// more than a millisecond passed?
|
||||
if (curTicks != lastCountTicks)
|
||||
{
|
||||
lastCountTicks = curTicks;
|
||||
lastCount = Value;
|
||||
}
|
||||
|
||||
return lastCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the counter by 1.
|
||||
/// </summary>
|
||||
public void Increment()
|
||||
{
|
||||
int curCellCount = this.cellCount;
|
||||
var drift = increment(ref GetCountRef(curCellCount));
|
||||
|
||||
if (drift != 0)
|
||||
{
|
||||
TryAddCell(curCellCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the counter by 1.
|
||||
/// </summary>
|
||||
public void Decrement()
|
||||
{
|
||||
int curCellCount = this.cellCount;
|
||||
var drift = decrement(ref GetCountRef(curCellCount));
|
||||
|
||||
if (drift != 0)
|
||||
{
|
||||
TryAddCell(curCellCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the counter by 'value'.
|
||||
/// </summary>
|
||||
public void Add(int value)
|
||||
{
|
||||
int curCellCount = this.cellCount;
|
||||
var drift = add(ref GetCountRef(curCellCount), value);
|
||||
|
||||
if (drift != 0)
|
||||
{
|
||||
TryAddCell(curCellCount);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ref long GetCountRef(int curCellCount)
|
||||
{
|
||||
ref var countRef = ref count;
|
||||
|
||||
Cell[]? cells;
|
||||
if ((cells = this.cells) != null && curCellCount > 1)
|
||||
{
|
||||
var cell = cells[GetIndex((uint)curCellCount)];
|
||||
if (cell != null)
|
||||
{
|
||||
countRef = ref cell.counter.count;
|
||||
}
|
||||
}
|
||||
|
||||
return ref countRef;
|
||||
}
|
||||
|
||||
private static long increment(ref long val)
|
||||
{
|
||||
return -val - 1 + Interlocked.Increment(ref val);
|
||||
}
|
||||
|
||||
private static long add(ref long val, int inc)
|
||||
{
|
||||
return -val - inc + Interlocked.Add(ref val, inc);
|
||||
}
|
||||
|
||||
private static long decrement(ref long val)
|
||||
{
|
||||
return val - 1 - Interlocked.Decrement(ref val);
|
||||
}
|
||||
|
||||
private void TryAddCell(int curCellCount)
|
||||
{
|
||||
if (curCellCount < s_MaxCellCount)
|
||||
{
|
||||
TryAddCellCore(curCellCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryAddCellCore(int curCellCount)
|
||||
{
|
||||
var cells = this.cells;
|
||||
if (cells == null)
|
||||
{
|
||||
var newCells = new Cell[s_MaxCellCount];
|
||||
cells = Interlocked.CompareExchange(ref this.cells, newCells, null) ?? newCells;
|
||||
}
|
||||
|
||||
if (cells[curCellCount] == null)
|
||||
{
|
||||
Interlocked.CompareExchange(ref cells[curCellCount], new Cell(), null);
|
||||
}
|
||||
|
||||
if (this.cellCount == curCellCount)
|
||||
{
|
||||
Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount);
|
||||
//if (Interlocked.CompareExchange(ref this.cellCount, curCellCount + 1, curCellCount) == curCellCount)
|
||||
//{
|
||||
// System.Console.WriteLine(curCellCount + 1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Vladimir Sadov. All rights reserved.
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
/// <summary>
|
||||
/// Scalable counter base.
|
||||
/// </summary>
|
||||
public class CounterBase
|
||||
{
|
||||
private protected const int CACHE_LINE = 64;
|
||||
private protected const int OBJ_HEADER_SIZE = 8;
|
||||
|
||||
private protected static readonly int s_MaxCellCount = Util.AlignToPowerOfTwo(Environment.ProcessorCount) + 1;
|
||||
|
||||
// how many cells we have
|
||||
private protected int cellCount;
|
||||
|
||||
// delayed count time
|
||||
private protected uint lastCountTicks;
|
||||
|
||||
private protected CounterBase()
|
||||
{
|
||||
// touch static
|
||||
_ = s_MaxCellCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private protected unsafe static int GetIndex(uint cellCount)
|
||||
{
|
||||
nuint addr = (nuint)(&cellCount);
|
||||
return (int)(addr % cellCount);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
{
|
||||
internal static class Util
|
||||
{
|
||||
// returns 2^x >= size
|
||||
internal static int AlignToPowerOfTwo(int size)
|
||||
{
|
||||
Debug.Assert(size > 0);
|
||||
|
||||
size--;
|
||||
size |= size >> 1;
|
||||
size |= size >> 2;
|
||||
size |= size >> 4;
|
||||
size |= size >> 8;
|
||||
size |= size >> 16;
|
||||
return size + 1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -31,8 +31,8 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
/// <summary>最小个数。默认1</summary>
|
||||
public Int32 Min { get; set; } = 1;
|
||||
|
||||
/// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认10s</summary>
|
||||
public Int32 IdleTime { get; set; } = 10;
|
||||
/// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认60s</summary>
|
||||
public Int32 IdleTime { get; set; } = 60;
|
||||
|
||||
/// <summary>完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理,默认0s永不清理</summary>
|
||||
public Int32 AllIdleTime { get; set; } = 0;
|
||||
@@ -44,7 +44,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
private readonly ConcurrentQueue<Item> _free2 = new();
|
||||
|
||||
/// <summary>借出去的放在这</summary>
|
||||
private readonly ConcurrentDictionary<T, Item> _busy = new();
|
||||
private readonly NonBlockingDictionary<T, Item> _busy = new();
|
||||
|
||||
//private readonly Object SyncRoot = new();
|
||||
#endregion
|
||||
@@ -126,9 +126,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
if (Max > 0 && count >= Max)
|
||||
{
|
||||
var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}";
|
||||
|
||||
WriteLog("Acquire Max " + msg);
|
||||
|
||||
throw new Exception(Name + " " + msg);
|
||||
}
|
||||
|
||||
@@ -268,7 +266,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
{
|
||||
if (_timer != null) return;
|
||||
|
||||
_timer = new TimerX(Work, null, 5000, 5000) { Async = true };
|
||||
_timer = new TimerX(Work, null, 60000, 60000) { Async = true };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +279,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
var count = 0;
|
||||
|
||||
// 清理过期不还。避免有借没还
|
||||
if (!_busy.IsEmpty)
|
||||
if (AllIdleTime > 0 && !_busy.IsEmpty)
|
||||
{
|
||||
var exp = TimerX.Now.AddSeconds(-AllIdleTime);
|
||||
foreach (var item in _busy)
|
||||
|
262
src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
Normal file
262
src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using ThingsGateway.NewLife.Log;
|
||||
using ThingsGateway.NewLife.Reflection;
|
||||
|
||||
namespace ThingsGateway.NewLife.Collections;
|
||||
|
||||
/// <summary>资源池。支持空闲释放,主要用于数据库连接池和网络连接池</summary>
|
||||
/// <remarks>
|
||||
/// 文档 https://newlifex.com/core/object_pool
|
||||
/// </remarks>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
{
|
||||
#region 属性
|
||||
/// <summary>名称</summary>
|
||||
public String Name { get; set; }
|
||||
|
||||
private Int32 _FreeCount;
|
||||
/// <summary>空闲个数</summary>
|
||||
public Int32 FreeCount => _FreeCount;
|
||||
|
||||
private Int32 _BusyCount;
|
||||
/// <summary>繁忙个数</summary>
|
||||
public Int32 BusyCount => _BusyCount;
|
||||
|
||||
/// <summary>最大个数。默认0,0表示无上限</summary>
|
||||
public Int32 Max { get; set; } = 0;
|
||||
|
||||
/// <summary>最小个数。默认1</summary>
|
||||
public Int32 Min { get; set; } = 1;
|
||||
|
||||
private readonly object _syncRoot = new();
|
||||
|
||||
/// <summary>基础空闲集合。只保存最小个数,最热部分</summary>
|
||||
private readonly Stack<T> _free = new();
|
||||
|
||||
/// <summary>扩展空闲集合。保存最小个数以外部分</summary>
|
||||
private readonly Queue<T> _free2 = new();
|
||||
|
||||
/// <summary>借出去的放在这</summary>
|
||||
private readonly HashSet<T> _busy = new();
|
||||
|
||||
//private readonly Object SyncRoot = new();
|
||||
#endregion
|
||||
|
||||
#region 构造
|
||||
/// <summary>实例化一个资源池</summary>
|
||||
public ObjectPoolLock()
|
||||
{
|
||||
var str = GetType().Name;
|
||||
if (str.Contains('`')) str = str.Substring(null, "`");
|
||||
if (str != "Pool")
|
||||
Name = str;
|
||||
else
|
||||
Name = $"Pool<{typeof(T).Name}>";
|
||||
|
||||
}
|
||||
~ObjectPoolLock()
|
||||
{
|
||||
this.TryDispose();
|
||||
}
|
||||
/// <summary>销毁</summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(Boolean disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
WriteLog($"Dispose {typeof(T).FullName} FreeCount={FreeCount:n0} BusyCount={BusyCount:n0}");
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
private volatile Boolean _inited;
|
||||
private void Init()
|
||||
{
|
||||
if (_inited) return;
|
||||
|
||||
lock (lockThis)
|
||||
{
|
||||
if (_inited) return;
|
||||
_inited = true;
|
||||
|
||||
WriteLog($"Init {typeof(T).FullName} Min={Min} Max={Max}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 主方法
|
||||
/// <summary>借出</summary>
|
||||
/// <returns></returns>
|
||||
public virtual T Get()
|
||||
{
|
||||
T? pi = null;
|
||||
do
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_free.Count > 0)
|
||||
{
|
||||
pi = _free.Pop();
|
||||
_FreeCount--;
|
||||
}
|
||||
else if (_free2.Count > 0)
|
||||
{
|
||||
pi = _free2.Dequeue();
|
||||
_FreeCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = BusyCount;
|
||||
if (Max > 0 && count >= Max)
|
||||
{
|
||||
var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}";
|
||||
WriteLog("Acquire Max " + msg);
|
||||
throw new Exception(Name + " " + msg);
|
||||
}
|
||||
|
||||
pi = OnCreate();
|
||||
if (count == 0) Init();
|
||||
|
||||
#if DEBUG
|
||||
WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, count + 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// 如果拿到的对象不可用,则重新借
|
||||
} while (pi == null || !OnGet(pi));
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
// 加入繁忙集合
|
||||
_busy.Add(pi);
|
||||
|
||||
_BusyCount++;
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
|
||||
/// <summary>借出时是否可用</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Boolean OnGet(T value) => true;
|
||||
|
||||
/// <summary>申请资源包装项,Dispose时自动归还到池中</summary>
|
||||
/// <returns></returns>
|
||||
public PoolItem<T> GetItem() => new(this, Get());
|
||||
|
||||
/// <summary>归还</summary>
|
||||
/// <param name="value"></param>
|
||||
public virtual Boolean Return(T value)
|
||||
{
|
||||
if (value == null) return false;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
// 从繁忙队列找到并移除缓存项
|
||||
if (!_busy.Remove(value))
|
||||
{
|
||||
#if DEBUG
|
||||
WriteLog("Return Error");
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_BusyCount--;
|
||||
}
|
||||
|
||||
// 是否可用
|
||||
if (!OnReturn(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value is DisposeBase db && db.Disposed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_FreeCount < Min)
|
||||
_free.Push(value);
|
||||
else
|
||||
_free2.Enqueue(value);
|
||||
_FreeCount++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>归还时是否可用</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Boolean OnReturn(T value) => true;
|
||||
|
||||
/// <summary>清空已有对象</summary>
|
||||
public virtual Int32 Clear()
|
||||
{
|
||||
var count = _FreeCount + _BusyCount;
|
||||
|
||||
//_busy.Clear();
|
||||
//_BusyCount = 0;
|
||||
|
||||
//_free.Clear();
|
||||
//while (_free2.TryDequeue(out var rs)) ;
|
||||
//_FreeCount = 0;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
count = _FreeCount + _BusyCount;
|
||||
|
||||
while (_free.Count > 0)
|
||||
{
|
||||
var pi = _free.Pop();
|
||||
OnDispose(pi);
|
||||
}
|
||||
|
||||
while (_free2.Count > 0)
|
||||
{
|
||||
var pi = _free2.Dequeue();
|
||||
OnDispose(pi);
|
||||
}
|
||||
|
||||
_FreeCount = 0;
|
||||
|
||||
foreach (var item in _busy)
|
||||
{
|
||||
OnDispose(item);
|
||||
}
|
||||
_busy.Clear();
|
||||
_BusyCount = 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>销毁</summary>
|
||||
/// <param name="value"></param>
|
||||
protected virtual void OnDispose(T? value) => value.TryDispose();
|
||||
#endregion
|
||||
|
||||
#region 重载
|
||||
/// <summary>创建实例</summary>
|
||||
/// <returns></returns>
|
||||
protected virtual T? OnCreate() => (T?)typeof(T).CreateInstance();
|
||||
#endregion
|
||||
protected object lockThis = new();
|
||||
|
||||
#region 日志
|
||||
/// <summary>日志</summary>
|
||||
public ILog Log { get; set; } = Logger.Null;
|
||||
|
||||
/// <summary>写日志</summary>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public void WriteLog(String format, params Object?[] args)
|
||||
{
|
||||
if (Log?.Enable != true) return;
|
||||
|
||||
Log.Info(Name + "." + format, args);
|
||||
}
|
||||
#endregion
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
namespace ThingsGateway.NewLife.Collections;
|
||||
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
internal struct ValueStopwatch
|
||||
{
|
||||
#if !NET7_0_OR_GREATER
|
||||
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
|
||||
#endif
|
||||
|
||||
private readonly long _startTimestamp;
|
||||
|
||||
public bool IsActive => _startTimestamp != 0;
|
||||
|
||||
private ValueStopwatch(long startTimestamp)
|
||||
{
|
||||
_startTimestamp = startTimestamp;
|
||||
}
|
||||
|
||||
public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
|
||||
|
||||
public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp)
|
||||
{
|
||||
#if !NET7_0_OR_GREATER
|
||||
var timestampDelta = endingTimestamp - startingTimestamp;
|
||||
var ticks = (long)(TimestampToTicks * timestampDelta);
|
||||
return new TimeSpan(ticks);
|
||||
#else
|
||||
return Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp);
|
||||
#endif
|
||||
}
|
||||
|
||||
public TimeSpan GetElapsedTime()
|
||||
{
|
||||
// Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0.
|
||||
// So it being 0 is a clear indication of default(ValueStopwatch)
|
||||
if (!IsActive)
|
||||
{
|
||||
throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.");
|
||||
}
|
||||
|
||||
var end = Stopwatch.GetTimestamp();
|
||||
|
||||
return GetElapsedTime(_startTimestamp, end);
|
||||
}
|
||||
}
|
@@ -5,30 +5,103 @@ namespace ThingsGateway.NewLife;
|
||||
|
||||
public class ExpiringDictionary<TKey, TValue> : IDisposable
|
||||
{
|
||||
private ConcurrentDictionary<TKey, TValue> _dict = new();
|
||||
private readonly TimerX _cleanupTimer;
|
||||
|
||||
public ExpiringDictionary(int cleanupInterval = 60000)
|
||||
/// <summary>缓存项</summary>
|
||||
public class CacheItem
|
||||
{
|
||||
_cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true };
|
||||
private TValue? _value;
|
||||
/// <summary>数值</summary>
|
||||
public TValue? Value { get => _value; }
|
||||
|
||||
/// <summary>过期时间。系统启动以来的毫秒数</summary>
|
||||
public Int64 ExpiredTime { get; set; }
|
||||
|
||||
/// <summary>是否过期</summary>
|
||||
public Boolean Expired => ExpiredTime <= Runtime.TickCount64;
|
||||
|
||||
/// <summary>访问时间</summary>
|
||||
public Int64 VisitTime { get; private set; }
|
||||
|
||||
/// <summary>构造缓存项</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="expire"></param>
|
||||
public CacheItem(TValue? value, Int32 expire) => Set(value, expire);
|
||||
|
||||
/// <summary>设置数值和过期时间</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="expire">过期时间,秒</param>
|
||||
public void Set(TValue value, Int32 expire)
|
||||
{
|
||||
_value = value;
|
||||
|
||||
var now = VisitTime = Runtime.TickCount64;
|
||||
if (expire <= 0)
|
||||
ExpiredTime = Int64.MaxValue;
|
||||
else
|
||||
ExpiredTime = now + expire * 1000L;
|
||||
}
|
||||
|
||||
/// <summary>更新访问时间并返回数值</summary>
|
||||
/// <returns></returns>
|
||||
public TValue? Visit()
|
||||
{
|
||||
VisitTime = Runtime.TickCount64;
|
||||
var rs = _value;
|
||||
if (rs == null) return default;
|
||||
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
private NonBlockingDictionary<TKey, CacheItem> _dict;
|
||||
|
||||
private readonly TimerX _cleanupTimer;
|
||||
private int defaultExpire = 60;
|
||||
IEqualityComparer<TKey>? comparer;
|
||||
public ExpiringDictionary(int expire = 60, IEqualityComparer<TKey>? comparer = null)
|
||||
{
|
||||
defaultExpire = expire;
|
||||
this.comparer = comparer;
|
||||
_dict = new NonBlockingDictionary<TKey, CacheItem>(comparer);
|
||||
|
||||
_cleanupTimer = new TimerX(TimerClear, null, 60000, 60000) { Async = true };
|
||||
}
|
||||
|
||||
public void TryAdd(TKey key, TValue value)
|
||||
public bool TryAdd(TKey key, TValue value)
|
||||
{
|
||||
_dict.TryAdd(key, value);
|
||||
if (_dict.TryGetValue(key, out var item))
|
||||
{
|
||||
if (!item.Expired) return false;
|
||||
item.Set(value, defaultExpire);
|
||||
return true;
|
||||
}
|
||||
return _dict.TryAdd(key, new CacheItem(value, defaultExpire));
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return _dict.TryGetValue(key, out value);
|
||||
value = default;
|
||||
|
||||
// 没有值,直接结束
|
||||
if (!_dict.TryGetValue(key, out var item) || item == null) return false;
|
||||
|
||||
// 得到已有值
|
||||
value = item.Visit();
|
||||
|
||||
// 是否未过期的有效值
|
||||
return !item.Expired;
|
||||
}
|
||||
public TValue GetOrAdd(TKey key, Func<TKey, TValue> func)
|
||||
{
|
||||
return _dict.GetOrAdd(key, func);
|
||||
}
|
||||
public TValue GetOrAdd(TKey key, TValue value)
|
||||
{
|
||||
return _dict.GetOrAdd(key, value);
|
||||
CacheItem? item = null;
|
||||
do
|
||||
{
|
||||
if (_dict.TryGetValue(key, out item) && item != null) return item.Visit();
|
||||
|
||||
item ??= new CacheItem(func(key), defaultExpire);
|
||||
}
|
||||
while (!_dict.TryAdd(key, item));
|
||||
|
||||
return item.Visit();
|
||||
|
||||
}
|
||||
|
||||
public bool TryRemove(TKey key) => _dict.TryRemove(key, out _);
|
||||
@@ -38,13 +111,38 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
|
||||
private void Clear(object? state)
|
||||
{
|
||||
var data = _dict;
|
||||
_dict = new();
|
||||
_dict = new(comparer);
|
||||
data.Clear();
|
||||
}
|
||||
private void TimerClear(object? state)
|
||||
{
|
||||
|
||||
var dic = _dict;
|
||||
if (dic.IsEmpty) return;
|
||||
|
||||
// 60分钟之内过期的数据,进入LRU淘汰
|
||||
var now = Runtime.TickCount64;
|
||||
|
||||
// 这里先计算,性能很重要
|
||||
var toDels = new List<TKey>();
|
||||
foreach (var item in dic)
|
||||
{
|
||||
// 已过期,准备删除
|
||||
var ci = item.Value;
|
||||
if (ci.ExpiredTime <= now)
|
||||
toDels.Add(item.Key);
|
||||
}
|
||||
|
||||
// 确认删除
|
||||
foreach (var item in toDels)
|
||||
{
|
||||
_dict.Remove(item);
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_dict.Clear();
|
||||
_cleanupTimer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,8 +13,8 @@ public class FastMapperOption
|
||||
public static class FastMapper
|
||||
{
|
||||
// 泛型 + 非泛型共用缓存
|
||||
private static readonly ConcurrentDictionary<(Type Source, Type Target), Delegate> _mapCache
|
||||
= new ConcurrentDictionary<(Type, Type), Delegate>();
|
||||
private static readonly NonBlockingDictionary<(Type Source, Type Target), Delegate> _mapCache
|
||||
= new NonBlockingDictionary<(Type, Type), Delegate>();
|
||||
|
||||
#region 泛型入口
|
||||
public static TTarget Mapper<TSource, TTarget>(TSource source, FastMapperOption option = null)
|
||||
|
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
public class LinkedCancellationTokenSourceCache : IDisposable
|
||||
{
|
||||
@@ -63,6 +63,7 @@ public class LinkedCancellationTokenSourceCache : IDisposable
|
||||
_cachedCts?.Dispose();
|
||||
_cachedCts = null!;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
@@ -10,13 +10,18 @@
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
public sealed class ReusableCancellationTokenSource : IDisposable
|
||||
{
|
||||
~ReusableCancellationTokenSource()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private readonly Timer _timer;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
@@ -29,11 +34,17 @@ public sealed class ReusableCancellationTokenSource : IDisposable
|
||||
|
||||
private void OnTimeout(object? state)
|
||||
{
|
||||
TimeoutStatus = true;
|
||||
try
|
||||
{
|
||||
TimeoutStatus = true;
|
||||
|
||||
if (_cts?.IsCancellationRequested == false)
|
||||
_cts?.Cancel();
|
||||
if (_cts?.IsCancellationRequested == false)
|
||||
_cts?.Cancel();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
|
||||
@@ -41,7 +52,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable
|
||||
/// <summary>
|
||||
/// 获取一个 CTS,并启动超时
|
||||
/// </summary>
|
||||
public CancellationTokenSource GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default)
|
||||
public CancellationToken GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default)
|
||||
{
|
||||
TimeoutStatus = false;
|
||||
|
||||
@@ -51,7 +62,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable
|
||||
// 启动 Timer
|
||||
_timer.Change(timeout, Timeout.Infinite);
|
||||
|
||||
return _cts;
|
||||
return _cts.Token;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,15 +76,16 @@ public sealed class ReusableCancellationTokenSource : IDisposable
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
_cts?.SafeCancel();
|
||||
try { _cts?.Cancel(); } catch { }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.SafeCancel();
|
||||
_cts?.SafeDispose();
|
||||
_linkedCtsCache.SafeDispose();
|
||||
_timer.SafeDispose();
|
||||
try { _cts?.Cancel(); } catch { }
|
||||
try { _cts?.Dispose(); } catch { }
|
||||
try { _linkedCtsCache?.Dispose(); } catch { }
|
||||
try { _timer?.Dispose(); } catch { }
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.NewLife.Log;
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
@@ -92,21 +92,119 @@ public sealed class WaitLock : IDisposable
|
||||
/// </summary>
|
||||
public Task WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _waiterLock.WaitAsync(cancellationToken);
|
||||
#if NET6_0_OR_GREATER
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
return WaitUntilCountOrTimeoutAsync(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken);
|
||||
//return WaitUntilAsync2(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken);
|
||||
else
|
||||
return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None);
|
||||
|
||||
#else
|
||||
return _waiterLock.WaitAsync(Timeout.Infinite, cancellationToken);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
|
||||
//private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new();
|
||||
|
||||
/// <summary>Performs the asynchronous wait.</summary>
|
||||
/// <param name="asyncWaiter">The asynchronous waiter.</param>
|
||||
/// <param name="millisecondsTimeout">The timeout.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The task to return to the caller.</returns>
|
||||
private Task<bool> WaitUntilCountOrTimeoutAsync(Task<bool> asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (millisecondsTimeout == Timeout.Infinite)
|
||||
{
|
||||
return (asyncWaiter.WaitAsync(cancellationToken));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
//private Task WaitUntilAsync2(Task task, int timeoutMs, CancellationToken token)
|
||||
//{
|
||||
// if (task.IsCompleted) return task;
|
||||
|
||||
// var tcs = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
// var reusableTimeout = _reusableTimeouts.Get();
|
||||
|
||||
// CancellationTokenRegistration ctr = default;
|
||||
|
||||
// // 超时 + 取消 Token
|
||||
// if (timeoutMs != Timeout.Infinite || token.CanBeCanceled)
|
||||
// {
|
||||
// var ctsToken = reusableTimeout.GetTokenSource(timeoutMs, token);
|
||||
|
||||
// ctr = ctsToken.Register(static (state, token2) =>
|
||||
// {
|
||||
// var (tcs2, ctoken) = ((TaskCompletionSource<object?>, CancellationToken))state!;
|
||||
// if (ctoken.IsCancellationRequested)
|
||||
// tcs2.TrySetCanceled(ctoken);
|
||||
// else
|
||||
// tcs2.TrySetException(new TimeoutException("The operation has timed out."));
|
||||
// }, (tcs, token));
|
||||
// }
|
||||
|
||||
// if (task.IsCompleted)
|
||||
// {
|
||||
// _reusableTimeouts.Return(reusableTimeout);
|
||||
// ctr.Dispose();
|
||||
// return task;
|
||||
// }
|
||||
|
||||
// // 监听原始任务
|
||||
// task.ContinueWith(static (t, state) =>
|
||||
// {
|
||||
// var (tcs2, ctr2, ctsPool, cts) = ((TaskCompletionSource<object?>, CancellationTokenRegistration, ObjectPoolLock<ReusableCancellationTokenSource>, ReusableCancellationTokenSource))state!;
|
||||
// try
|
||||
// {
|
||||
// if (t.IsCanceled)
|
||||
// tcs2.TrySetCanceled();
|
||||
// else if (t.IsFaulted)
|
||||
// tcs2.TrySetException(t.Exception!.InnerExceptions);
|
||||
// else
|
||||
// tcs2.TrySetResult(null);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// ctsPool.Return(cts);
|
||||
// ctr2.Dispose();
|
||||
// }
|
||||
// }, (tcs, ctr, _reusableTimeouts, reusableTimeout), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
|
||||
// return tcs.Task;
|
||||
//}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
if (cancellationToken.CanBeCanceled || millisecondsTimeout != Timeout.Infinite)
|
||||
return WaitUntilCountOrTimeoutAsync(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), millisecondsTimeout, cancellationToken);
|
||||
else
|
||||
return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None);
|
||||
|
||||
#else
|
||||
return _waiterLock.WaitAsync(millisecondsTimeout, cancellationToken);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DisposedValue;
|
||||
public void Dispose()
|
||||
{
|
||||
DisposedValue = true;
|
||||
#if NET6_0_OR_GREATER
|
||||
//_reusableTimeouts?.TryDispose();
|
||||
#endif
|
||||
_waiterLock?.TryDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
@@ -168,8 +168,8 @@ public class CompositeConfigProvider : IConfigProvider
|
||||
#endregion
|
||||
|
||||
#region 绑定
|
||||
private readonly ConcurrentDictionary<Object, String> _models = [];
|
||||
private readonly ConcurrentDictionary<Object, ModelWrap> _models2 = [];
|
||||
private readonly NonBlockingDictionary<Object, String> _models = [];
|
||||
private readonly NonBlockingDictionary<Object, ModelWrap> _models2 = [];
|
||||
/// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
|
||||
/// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
|
||||
/// <param name="model">模型实例</param>
|
||||
|
@@ -11,7 +11,7 @@ public static class DictionaryExtensions
|
||||
/// <param name="dict"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static Boolean Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key) where TKey : notnull => dict.TryRemove(key, out _);
|
||||
public static Boolean Remove<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dict, TKey key) where TKey : notnull => dict.TryRemove(key, out _);
|
||||
|
||||
#if !NET6_0_OR_GREATER
|
||||
public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> pairs, TKey key, TValue value)
|
||||
@@ -77,7 +77,7 @@ public static class DictionaryExtensions
|
||||
/// <summary>
|
||||
/// 批量出队
|
||||
/// </summary>
|
||||
public static List<T> ToListWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0)
|
||||
public static List<T> ToListWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0)
|
||||
{
|
||||
if (maxCount <= 0)
|
||||
{
|
||||
@@ -105,7 +105,7 @@ public static class DictionaryExtensions
|
||||
/// <summary>
|
||||
/// 批量出队
|
||||
/// </summary>
|
||||
public static Dictionary<TKEY, T> ToDictWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0)
|
||||
public static Dictionary<TKEY, T> ToDictWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0)
|
||||
{
|
||||
if (maxCount <= 0)
|
||||
{
|
||||
@@ -135,7 +135,7 @@ public static class DictionaryExtensions
|
||||
/// <summary>
|
||||
/// 批量出队
|
||||
/// </summary>
|
||||
public static IEnumerable<KeyValuePair<TKEY, T>> ToIEnumerableKVWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0)
|
||||
public static IEnumerable<KeyValuePair<TKEY, T>> ToIEnumerableKVWithDequeue<TKEY, T>(this NonBlockingDictionary<TKEY, T> values, int maxCount = 0)
|
||||
{
|
||||
if (values.IsEmpty) yield break;
|
||||
|
||||
|
@@ -101,7 +101,7 @@ public static class LinqExtensions
|
||||
/// <summary>
|
||||
/// 从并发字典中删除
|
||||
/// </summary>
|
||||
public static bool Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key) where TKey : notnull
|
||||
public static bool Remove<TKey, TValue>(this NonBlockingDictionary<TKey, TValue> dict, TKey key) where TKey : notnull
|
||||
{
|
||||
return dict.TryRemove(key, out TValue? _);
|
||||
}
|
||||
|
@@ -38,10 +38,8 @@ public static class PathExtensions
|
||||
path = Path.Combine(path, text);
|
||||
}
|
||||
}
|
||||
// 处理路径分隔符,兼容Windows和Linux
|
||||
var sep = Path.DirectorySeparatorChar;
|
||||
var sep2 = sep == '/' ? '\\' : '/';
|
||||
path = path.Replace(sep2, sep);
|
||||
|
||||
path = path.Replace('\\', '/');
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
@@ -463,7 +463,7 @@ public static class ProcessHelper
|
||||
StandardOutputEncoding = outputEncoding,
|
||||
StandardErrorEncoding = outputEncoding,
|
||||
};
|
||||
var process = Process.Start(psi);
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return null;
|
||||
|
||||
if (msWait > 0 && !process.WaitForExit(msWait))
|
||||
|
@@ -97,7 +97,7 @@ public class ConsoleLog : Logger
|
||||
}
|
||||
}
|
||||
|
||||
static readonly ConcurrentDictionary<Int32, ConsoleColor> dic = new();
|
||||
static readonly NonBlockingDictionary<Int32, ConsoleColor> dic = new();
|
||||
static readonly ConsoleColor[] colors = [
|
||||
ConsoleColor.Green, ConsoleColor.Cyan, ConsoleColor.Magenta, ConsoleColor.White, ConsoleColor.Yellow,
|
||||
ConsoleColor.DarkGreen, ConsoleColor.DarkCyan, ConsoleColor.DarkMagenta, ConsoleColor.DarkRed, ConsoleColor.DarkYellow ];
|
||||
|
@@ -109,7 +109,7 @@ public class DefaultTracer : DisposeBase, ITracer, ILogFeature
|
||||
public IPool<ISpan> SpanPool => _SpanPool ??= new MySpanPool();
|
||||
|
||||
/// <summary>Span构建器集合</summary>
|
||||
protected ConcurrentDictionary<String, ISpanBuilder> _builders = new();
|
||||
protected NonBlockingDictionary<String, ISpanBuilder> _builders = new();
|
||||
|
||||
/// <summary>采样定时器</summary>
|
||||
protected TimerX? _timer;
|
||||
@@ -292,7 +292,7 @@ public class DefaultTracer : DisposeBase, ITracer, ILogFeature
|
||||
var bs = _builders;
|
||||
if (bs.IsEmpty) return [];
|
||||
|
||||
_builders = new ConcurrentDictionary<String, ISpanBuilder>();
|
||||
_builders = new NonBlockingDictionary<String, ISpanBuilder>();
|
||||
|
||||
var bs2 = bs.Values.Where(e => e.Total > 0).ToArray();
|
||||
|
||||
|
@@ -64,7 +64,7 @@ public class TextFileLog : Logger, IDisposable
|
||||
_Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000) { Async = true };
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly NonBlockingDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
/// <summary>每个目录的日志实例应该只有一个,所以采用静态创建</summary>
|
||||
/// <param name="path">日志目录或日志文件路径</param>
|
||||
/// <param name="fileFormat"></param>
|
||||
@@ -234,7 +234,7 @@ public class TextFileLog : Logger, IDisposable
|
||||
if (!_isFile && Backups > 0)
|
||||
{
|
||||
// 判断日志目录是否已存在
|
||||
var di = LogPath.GetBasePath().AsDirectory();
|
||||
DirectoryInfo? di = new DirectoryInfo(LogPath);
|
||||
if (di.Exists)
|
||||
{
|
||||
// 删除*.del
|
||||
@@ -257,7 +257,7 @@ public class TextFileLog : Logger, IDisposable
|
||||
{
|
||||
// 删除最旧的文件
|
||||
var retain = fis.Length - Backups;
|
||||
fis = fis.OrderBy(e => e.CreationTime).Take(retain).ToArray();
|
||||
fis = fis.OrderBy(e => e.LastWriteTimeUtc).Take(retain).ToArray();
|
||||
foreach (var item in fis)
|
||||
{
|
||||
OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length);
|
||||
|
@@ -59,7 +59,7 @@ public interface IEventHandler<TEvent>
|
||||
/// </remarks>
|
||||
public class EventBus<TEvent> : DisposeBase, IEventBus<TEvent>
|
||||
{
|
||||
private readonly ConcurrentDictionary<String, IEventHandler<TEvent>> _handlers = [];
|
||||
private readonly NonBlockingDictionary<String, IEventHandler<TEvent>> _handlers = [];
|
||||
/// <summary>已订阅的事件处理器集合</summary>
|
||||
public IDictionary<String, IEventHandler<TEvent>> Handlers => _handlers;
|
||||
|
||||
|
@@ -20,9 +20,9 @@ public class DeferredQueue : DisposeBase
|
||||
/// <summary>名称</summary>
|
||||
public String Name { get; set; }
|
||||
|
||||
private volatile ConcurrentDictionary<String, Object> _Entities = new();
|
||||
private volatile NonBlockingDictionary<String, Object> _Entities = new();
|
||||
/// <summary>实体字典</summary>
|
||||
public ConcurrentDictionary<String, Object> Entities => _Entities;
|
||||
public NonBlockingDictionary<String, Object> Entities => _Entities;
|
||||
|
||||
/// <summary>跟踪数。达到该值时输出跟踪日志,默认1000</summary>
|
||||
public Int32 TraceCount { get; set; } = 1000;
|
||||
@@ -206,7 +206,7 @@ public class DeferredQueue : DisposeBase
|
||||
var es = _Entities;
|
||||
if (es.IsEmpty) return;
|
||||
|
||||
_Entities = new ConcurrentDictionary<String, Object>();
|
||||
_Entities = new NonBlockingDictionary<String, Object>();
|
||||
var times = _Times;
|
||||
|
||||
Interlocked.Add(ref _count, -es.Count);
|
||||
|
@@ -18,7 +18,7 @@ class MyServiceScope : IServiceScope, IServiceProvider
|
||||
|
||||
public IServiceProvider ServiceProvider => this;
|
||||
|
||||
private readonly ConcurrentDictionary<Type, Object?> _cache = new();
|
||||
private readonly NonBlockingDictionary<Type, Object?> _cache = new();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@@ -23,7 +23,7 @@ public class DnsResolver : IDnsResolver
|
||||
/// <summary>缓存超时时间</summary>
|
||||
public TimeSpan Expire { set; get; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
private readonly ConcurrentDictionary<String, DnsItem> _cache = new();
|
||||
private readonly NonBlockingDictionary<String, DnsItem> _cache = new();
|
||||
|
||||
/// <summary>解析域名</summary>
|
||||
/// <param name="host"></param>
|
||||
|
@@ -146,7 +146,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature
|
||||
/// </remarks>
|
||||
public IServiceProvider? ServiceProvider { get; set; }
|
||||
|
||||
private ConcurrentDictionary<String, Object?>? _items;
|
||||
private NonBlockingDictionary<String, Object?>? _items;
|
||||
/// <summary>数据项</summary>
|
||||
public IDictionary<String, Object?> Items => _items ??= new();
|
||||
|
||||
@@ -486,7 +486,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature
|
||||
#endregion
|
||||
|
||||
#region 会话
|
||||
private readonly ConcurrentDictionary<Int32, INetSession> _Sessions = new();
|
||||
private readonly NonBlockingDictionary<Int32, INetSession> _Sessions = new();
|
||||
/// <summary>会话集合。用自增的数字ID作为标识,业务应用自己维持ID与业务主键的对应关系。</summary>
|
||||
public IDictionary<Int32, INetSession> Sessions => _Sessions;
|
||||
|
||||
|
@@ -853,7 +853,7 @@ public abstract class SessionBase : DisposeBase, ISocketClient, ITransport, ILog
|
||||
#endregion 异常处理
|
||||
|
||||
#region 扩展接口
|
||||
private ConcurrentDictionary<String, Object?>? _items;
|
||||
private NonBlockingDictionary<String, Object?>? _items;
|
||||
/// <summary>数据项</summary>
|
||||
public IDictionary<String, Object?> Items => _items ??= new();
|
||||
|
||||
|
@@ -10,7 +10,7 @@ namespace ThingsGateway.NewLife.Net;
|
||||
internal class SessionCollection : DisposeBase, IDictionary<String, ISocketSession>
|
||||
{
|
||||
#region 属性
|
||||
private readonly ConcurrentDictionary<String, ISocketSession> _dic = new();
|
||||
private readonly NonBlockingDictionary<String, ISocketSession> _dic = new();
|
||||
|
||||
/// <summary>服务端</summary>
|
||||
public ISocketServer Server { get; private set; }
|
||||
|
@@ -400,7 +400,7 @@ public class UdpSession : DisposeBase, ISocketSession, ITransport, ILogFeature
|
||||
#endregion
|
||||
|
||||
#region 扩展接口
|
||||
private ConcurrentDictionary<String, Object?>? _items;
|
||||
private NonBlockingDictionary<String, Object?>? _items;
|
||||
/// <summary>数据项</summary>
|
||||
public IDictionary<String, Object?> Items => _items ??= new();
|
||||
|
||||
|
@@ -0,0 +1,15 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides async/await-related extension methods
|
||||
/// </summary>
|
||||
public static class AwaitableExtensions
|
||||
{
|
||||
/// <summary>Controls whether a yield operation should respect captured context</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable _, bool continueOnCapturedContext)
|
||||
=> new ConfiguredYieldAwaitable(continueOnCapturedContext);
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>Provides an awaitable context for switching into a target environment.</summary>
|
||||
public readonly struct ConfiguredYieldAwaitable
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is ConfiguredYieldAwaitable other && other._continueOnCapturedContext == _continueOnCapturedContext;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(ConfiguredYieldAwaitable);
|
||||
|
||||
private readonly bool _continueOnCapturedContext;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ConfiguredYieldAwaitable(bool continueOnCapturedContext)
|
||||
=> _continueOnCapturedContext = continueOnCapturedContext;
|
||||
|
||||
/// <summary>Gets an awaiter for this <see cref="ConfiguredYieldAwaitable"/>.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ConfiguredYieldAwaiter GetAwaiter()
|
||||
=> new ConfiguredYieldAwaiter(_continueOnCapturedContext);
|
||||
|
||||
/// <summary>Provides an awaitable context for switching into a target environment.</summary>
|
||||
public readonly struct ConfiguredYieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is ConfiguredYieldAwaiter other && other._continueOnCapturedContext == _continueOnCapturedContext;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(ConfiguredYieldAwaiter);
|
||||
|
||||
private readonly bool _continueOnCapturedContext;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ConfiguredYieldAwaiter(bool continueOnCapturedContext)
|
||||
=> _continueOnCapturedContext = continueOnCapturedContext;
|
||||
|
||||
/// <summary>Gets whether a yield is not required.</summary>
|
||||
public bool IsCompleted
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => false;
|
||||
}
|
||||
|
||||
/// <summary>Ends the await operation.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void GetResult() { }
|
||||
|
||||
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
if (_continueOnCapturedContext) YieldFlowContext(continuation, true);
|
||||
else YieldNoContext(continuation, true);
|
||||
}
|
||||
|
||||
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
if (_continueOnCapturedContext) YieldFlowContext(continuation, false);
|
||||
else YieldNoContext(continuation, false);
|
||||
}
|
||||
|
||||
private static readonly WaitCallback s_waitCallbackRunAction = state => ((Action?)state)?.Invoke();
|
||||
|
||||
#if PLAT_THREADPOOLWORKITEM
|
||||
private sealed class ContinuationWorkItem : IThreadPoolWorkItem
|
||||
{
|
||||
private Action? _continuation;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ContinuationWorkItem() => Internal.Counters.ItemBoxAllocated.Increment();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ContinuationWorkItem Create(Action continuation)
|
||||
{
|
||||
var box = Pool<ContinuationWorkItem>.TryGet() ?? new ContinuationWorkItem();
|
||||
box._continuation = continuation;
|
||||
return box;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void IThreadPoolWorkItem.Execute()
|
||||
{
|
||||
var callback = _continuation;
|
||||
_continuation = null;
|
||||
Pool<ContinuationWorkItem>.TryPut(this);
|
||||
callback?.Invoke();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
[MethodImpl(MethodImplOptions.NoInlining)] // no-one ever calls ConfigureAwait(true)!
|
||||
private static void YieldFlowContext(Action continuation, bool flowContext)
|
||||
{
|
||||
var awaiter = default(YieldAwaitable.YieldAwaiter);
|
||||
if (flowContext) awaiter.OnCompleted(continuation);
|
||||
else awaiter.UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void YieldNoContext(Action continuation, bool flowContext)
|
||||
{
|
||||
if (flowContext)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if PLAT_THREADPOOLWORKITEM
|
||||
ThreadPool.UnsafeQueueUserWorkItem(ContinuationWorkItem.Create(continuation), false);
|
||||
#elif NETSTANDARD1_3
|
||||
ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||
#else
|
||||
ThreadPool.UnsafeQueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an operation that completes at the first incomplete await,
|
||||
/// with the remainder continuing in the background
|
||||
/// </summary>
|
||||
[AsyncMethodBuilder(typeof(MethodBuilders.FireAndForgetMethodBuilder))]
|
||||
public readonly struct FireAndForget
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(FireAndForget);
|
||||
|
||||
/// <summary>
|
||||
/// Raised when exceptions occur on fire-and-forget methods
|
||||
/// </summary>
|
||||
public static event Action<Exception>? Exception;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void OnException(Exception exception)
|
||||
{
|
||||
if (exception != null) Exception?.Invoke(exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a value-task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueTask AsValueTask() => default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Task AsTask() => TaskUtils.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator Task(FireAndForget _) => TaskUtils.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a value-task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ValueTask(FireAndForget _) => default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the awaiter for the instance
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueTaskAwaiter GetAwaiter() => default(ValueTask).GetAwaiter();
|
||||
}
|
||||
}
|
13
src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs
Normal file
13
src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that an object can be reset
|
||||
/// </summary>
|
||||
public interface IResettable
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets this instance
|
||||
/// </summary>
|
||||
public void Reset();
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
#if NETSTANDARD1_3
|
||||
using System;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
|
||||
internal sealed class BrowsableAttribute : Attribute
|
||||
{
|
||||
public BrowsableAttribute(bool _) { }
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,68 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal static class Counters
|
||||
{
|
||||
internal struct Counter
|
||||
{
|
||||
private long _value;
|
||||
[Conditional("DEBUG")]
|
||||
public void Increment() => Interlocked.Increment(ref _value);
|
||||
#if !DEBUG
|
||||
[System.Obsolete("Release only", false)]
|
||||
#endif
|
||||
public long Value => Interlocked.Read(ref _value);
|
||||
#pragma warning disable CS0618
|
||||
public override string ToString() => Value.ToString();
|
||||
#pragma warning restore CS0618
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public void Reset() => Interlocked.Exchange(ref _value, 0);
|
||||
}
|
||||
|
||||
internal static Counter
|
||||
SetStateMachine,
|
||||
PooledStateAllocated,
|
||||
PooledStateRecycled,
|
||||
StateMachineBoxAllocated,
|
||||
StateMachineBoxRecycled,
|
||||
ItemBoxAllocated,
|
||||
TaskAllocated,
|
||||
LazyStateAllocated;
|
||||
|
||||
#if !DEBUG
|
||||
[System.Obsolete("Release only", false)]
|
||||
#endif
|
||||
public static long TotalAllocations =>
|
||||
PooledStateAllocated.Value + StateMachineBoxAllocated.Value
|
||||
+ ItemBoxAllocated.Value + TaskAllocated.Value
|
||||
+ SetStateMachine.Value // SetStateMachine usually means a boxed value
|
||||
+ LazyStateAllocated.Value;
|
||||
|
||||
internal static void Reset()
|
||||
{
|
||||
SetStateMachine.Reset();
|
||||
PooledStateAllocated.Reset();
|
||||
PooledStateRecycled.Reset();
|
||||
StateMachineBoxAllocated.Reset();
|
||||
StateMachineBoxRecycled.Reset();
|
||||
ItemBoxAllocated.Reset();
|
||||
TaskAllocated.Reset();
|
||||
LazyStateAllocated.Reset();
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
[System.Obsolete("Release only", false)]
|
||||
#endif
|
||||
internal static string Summary()
|
||||
=> $@"SetStateMachine: {SetStateMachine.Value}
|
||||
PooledStateAllocated: {PooledStateAllocated.Value}
|
||||
PooledStateRecycled: {PooledStateRecycled.Value}
|
||||
StateMachineBoxAllocated: {StateMachineBoxAllocated.Value}
|
||||
StateMachineBoxRecycled: {StateMachineBoxRecycled.Value}
|
||||
ItemBoxAllocated: {ItemBoxAllocated.Value}
|
||||
TaskAllocated: {TaskAllocated.Value}
|
||||
LazyStateAllocated: {LazyStateAllocated.Value}";
|
||||
}
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal sealed class LazyTaskState<T>
|
||||
{
|
||||
private short _version;
|
||||
private T _result;
|
||||
private Exception? _exception;
|
||||
private Task? _task;
|
||||
private bool _isComplete;
|
||||
private ValueTaskCompletionSource<T> _source;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CheckTokenInsideLock(short token)
|
||||
{
|
||||
if (token != _version) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
private object _syncRoot = new object();
|
||||
public Task GetTask(short token)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
CheckTokenInsideLock(token);
|
||||
if (_task != null) { }
|
||||
else if (_exception is OperationCanceledException) _task = TaskUtils.TaskFactory<T>.Canceled;
|
||||
else if (_exception != null) _task = TaskUtils.FromException<T>(_exception);
|
||||
else if (_isComplete) _task = typeof(T) == typeof(Nothing) ? TaskUtils.CompletedTask : TaskUtils.TaskFactory<T>.FromResult(_result);
|
||||
else
|
||||
{
|
||||
_source = ValueTaskCompletionSource<T>.Create();
|
||||
_task = _source.Task;
|
||||
}
|
||||
return _task;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsValid(short token) => Volatile.Read(ref _version) == token;
|
||||
internal bool HasSource
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot) { return !_source.IsNull; }
|
||||
}
|
||||
}
|
||||
internal bool HasTask => Volatile.Read(ref _task) != null;
|
||||
|
||||
public bool TrySetResult(short token, T result)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_isComplete) return false;
|
||||
if (token != _version) return false;
|
||||
_isComplete = true;
|
||||
if (!_source.IsNull) return _source.TrySetResult(result);
|
||||
_result = result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySetException(short token, Exception exception)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_isComplete) return false;
|
||||
if (token != _version) return false;
|
||||
_isComplete = true;
|
||||
if (!_source.IsNull) return _source.TrySetException(exception);
|
||||
_exception = exception;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySetCanceled(short token, CancellationToken cancellationToken = default)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_isComplete) return false;
|
||||
if (token != _version) return false;
|
||||
_isComplete = true;
|
||||
if (!_source.IsNull) return _source.TrySetCanceled(cancellationToken);
|
||||
_task = TaskUtils.TaskFactory<T>.Canceled;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskState<T> Create() => Pool<LazyTaskState<T>>.TryGet() ?? new LazyTaskState<T>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private LazyTaskState()
|
||||
{
|
||||
Counters.LazyStateAllocated.Increment();
|
||||
_result = default!;
|
||||
_version = InitialVersion;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskState<T> CreateConstant(T value)
|
||||
{
|
||||
var obj = new LazyTaskState<T>
|
||||
{
|
||||
_version = Constant
|
||||
};
|
||||
obj.TrySetResult(Constant, value);
|
||||
return obj;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskState<T> CreateCanceled()
|
||||
{
|
||||
var obj = new LazyTaskState<T>
|
||||
{
|
||||
_version = Constant
|
||||
};
|
||||
obj.TrySetCanceled(Constant);
|
||||
return obj;
|
||||
}
|
||||
|
||||
const short InitialVersion = 0, Constant = InitialVersion - 1;
|
||||
|
||||
internal short Version
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _version;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal void Recycle(short token)
|
||||
{
|
||||
if (token == Constant) return; // never recycle constant values; this is by design
|
||||
|
||||
if (Volatile.Read(ref _version) != token) return; // wrong version; all bets are off!
|
||||
|
||||
if (!Volatile.Read(ref _isComplete)) // if incomplete, try to cancel
|
||||
{
|
||||
if (!TrySetCanceled(token)) return; // if that didn't work... give up - don't recycle
|
||||
}
|
||||
|
||||
bool haveLock = false;
|
||||
try
|
||||
{
|
||||
// only if uncontested; we're not waiting in a dispose
|
||||
Monitor.TryEnter(_syncRoot, ref haveLock);
|
||||
if (haveLock)
|
||||
{
|
||||
if (token == _version)
|
||||
{
|
||||
_result = default!;
|
||||
_exception = default;
|
||||
_task = default;
|
||||
_isComplete = false;
|
||||
_source = default;
|
||||
|
||||
switch (++_version)
|
||||
{
|
||||
case InitialVersion: // don't wrap all the way around when recycling; could lead to conflicts
|
||||
case Constant: // don't allow things to *become* constants
|
||||
break;
|
||||
default:
|
||||
Pool<LazyTaskState<T>>.TryPut(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (haveLock) Monitor.Exit(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,314 @@
|
||||
#if !PLAT_MRVTSC
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
// from: https://raw.githubusercontent.com/dotnet/coreclr/master/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs
|
||||
// original license:
|
||||
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal readonly struct WaitCallbackShim
|
||||
{
|
||||
public readonly Action<object?>? Continuation;
|
||||
public readonly object? State;
|
||||
private WaitCallbackShim(Action<object?>? continuation, object? state)
|
||||
{
|
||||
Continuation = continuation;
|
||||
State = state;
|
||||
}
|
||||
public static object Create(Action<object?>? continuation, object? state)
|
||||
=> Pool.Box(new WaitCallbackShim(continuation, state));
|
||||
|
||||
private void InvokeContinuation() => Continuation?.Invoke(State);
|
||||
|
||||
public static readonly WaitCallback Invoke = state => Pool.UnboxAndReturn<WaitCallbackShim>(state).InvokeContinuation();
|
||||
}
|
||||
|
||||
/// <summary>Provides the core logic for implementing a manual-reset <see cref="IValueTaskSource"/> or <see cref="IValueTaskSource{TResult}"/>.</summary>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public struct ManualResetValueTaskSourceCore<TResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// The callback to invoke when the operation completes if <see cref="OnCompleted"/> was called before the operation completed,
|
||||
/// or <see cref="ManualResetValueTaskSourceCoreShared.s_sentinel"/> if the operation completed before a callback was supplied,
|
||||
/// or null if a callback hasn't yet been provided and the operation hasn't yet completed.
|
||||
/// </summary>
|
||||
private Action<object?>? _continuation;
|
||||
/// <summary>State to pass to <see cref="_continuation"/>.</summary>
|
||||
private object? _continuationState;
|
||||
/// <summary><see cref="ExecutionContext"/> to flow to the callback, or null if no flowing is required.</summary>
|
||||
private ExecutionContext? _executionContext;
|
||||
/// <summary>
|
||||
/// A "captured" <see cref="SynchronizationContext"/> or <see cref="TaskScheduler"/> with which to invoke the callback,
|
||||
/// or null if no special context is required.
|
||||
/// </summary>
|
||||
private object? _capturedContext;
|
||||
/// <summary>Whether the current operation has completed.</summary>
|
||||
private bool _completed;
|
||||
/// <summary>The result with which the operation succeeded, or the default value if it hasn't yet completed or failed.</summary>
|
||||
/* [AllowNull, MaybeNull] */
|
||||
private TResult _result;
|
||||
/// <summary>The exception with which the operation failed, or null if it hasn't yet completed or completed successfully.</summary>
|
||||
private ExceptionDispatchInfo? _error;
|
||||
/// <summary>The current version of this value, used to help prevent misuse.</summary>
|
||||
private short _version;
|
||||
|
||||
/// <summary>Gets or sets whether to force continuations to run asynchronously.</summary>
|
||||
/// <remarks>Continuations may run asynchronously if this is false, but they'll never run synchronously if this is true.</remarks>
|
||||
public bool RunContinuationsAsynchronously { get; set; }
|
||||
|
||||
/// <summary>Resets to prepare for the next operation.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
// Reset/update state for the next use/await of this instance.
|
||||
_version++;
|
||||
_completed = false;
|
||||
_result = default!; // TODO-NULLABLE: Remove ! when nullable attributes are respected
|
||||
_error = null;
|
||||
_executionContext = null;
|
||||
_capturedContext = null;
|
||||
_continuation = null;
|
||||
_continuationState = null;
|
||||
}
|
||||
|
||||
/// <summary>Completes with a successful result.</summary>
|
||||
/// <param name="result">The result.</param>
|
||||
public void SetResult(TResult result)
|
||||
{
|
||||
_result = result;
|
||||
SignalCompletion();
|
||||
}
|
||||
|
||||
/// <summary>Complets with an error.</summary>
|
||||
/// <param name="error"></param>
|
||||
public void SetException(Exception error)
|
||||
{
|
||||
_error = ExceptionDispatchInfo.Capture(error);
|
||||
SignalCompletion();
|
||||
}
|
||||
|
||||
/// <summary>Gets the operation version.</summary>
|
||||
public short Version => _version;
|
||||
|
||||
/// <summary>Gets the status of the operation.</summary>
|
||||
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||
public ValueTaskSourceStatus GetStatus(short token)
|
||||
{
|
||||
ValidateToken(token);
|
||||
return
|
||||
_continuation == null || !_completed ? ValueTaskSourceStatus.Pending :
|
||||
_error == null ? ValueTaskSourceStatus.Succeeded :
|
||||
_error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled :
|
||||
ValueTaskSourceStatus.Faulted;
|
||||
}
|
||||
|
||||
/// <summary>Gets the result of the operation.</summary>
|
||||
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||
// [StackTraceHidden]
|
||||
public TResult GetResult(short token)
|
||||
{
|
||||
ValidateToken(token);
|
||||
if (!_completed)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
_error?.Throw();
|
||||
return _result;
|
||||
}
|
||||
|
||||
/// <summary>Schedules the continuation action for this operation.</summary>
|
||||
/// <param name="continuation">The continuation to invoke when the operation has completed.</param>
|
||||
/// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param>
|
||||
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||
/// <param name="flags">The flags describing the behavior of the continuation.</param>
|
||||
public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
||||
{
|
||||
if (continuation == null) ThrowHelper.ThrowArgumentNullException(nameof(continuation));
|
||||
ValidateToken(token);
|
||||
|
||||
if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0)
|
||||
{
|
||||
_executionContext = ExecutionContext.Capture();
|
||||
}
|
||||
|
||||
if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0)
|
||||
{
|
||||
SynchronizationContext? sc = SynchronizationContext.Current;
|
||||
if (sc != null && sc.GetType() != typeof(SynchronizationContext))
|
||||
{
|
||||
_capturedContext = sc;
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskScheduler ts = TaskScheduler.Current;
|
||||
if (ts != TaskScheduler.Default)
|
||||
{
|
||||
_capturedContext = ts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to set the continuation state before we swap in the delegate, so that
|
||||
// if there's a race between this and SetResult/Exception and SetResult/Exception
|
||||
// sees the _continuation as non-null, it'll be able to invoke it with the state
|
||||
// stored here. However, this also means that if this is used incorrectly (e.g.
|
||||
// awaited twice concurrently), _continuationState might get erroneously overwritten.
|
||||
// To minimize the chances of that, we check preemptively whether _continuation
|
||||
// is already set to something other than the completion sentinel.
|
||||
|
||||
object? oldContinuation = _continuation;
|
||||
if (oldContinuation == null)
|
||||
{
|
||||
_continuationState = state;
|
||||
oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null);
|
||||
}
|
||||
|
||||
if (oldContinuation != null)
|
||||
{
|
||||
// Operation already completed, so we need to queue the supplied callback.
|
||||
if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel))
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
switch (_capturedContext)
|
||||
{
|
||||
case null:
|
||||
if (_executionContext != null)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state));
|
||||
}
|
||||
else
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
#else
|
||||
ThreadPool.UnsafeQueueUserWorkItem(
|
||||
#endif
|
||||
WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state));
|
||||
}
|
||||
break;
|
||||
|
||||
case SynchronizationContext sc:
|
||||
sc.Post(s =>
|
||||
{
|
||||
var tuple = (Tuple<Action<object?>, object?>)s!;
|
||||
tuple.Item1(tuple.Item2);
|
||||
}, Tuple.Create(continuation, state));
|
||||
break;
|
||||
|
||||
case TaskScheduler ts:
|
||||
Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ensures that the specified token matches the current version.</summary>
|
||||
/// <param name="token">The token supplied by <see cref="ValueTask"/>.</param>
|
||||
private void ValidateToken(short token)
|
||||
{
|
||||
if (token != _version)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Signals that the operation has completed. Invoked after the result or error has been set.</summary>
|
||||
private void SignalCompletion()
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
_completed = true;
|
||||
|
||||
if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null)
|
||||
{
|
||||
if (_executionContext != null)
|
||||
{
|
||||
ExecutionContext.Run(
|
||||
_executionContext,
|
||||
s_UnboxAndInvokeContextCallback,
|
||||
Pool.Box<ManualResetValueTaskSourceCore<TResult>>(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
InvokeContinuation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static readonly ContextCallback s_UnboxAndInvokeContextCallback = state => Pool.UnboxAndReturn<ManualResetValueTaskSourceCore<TResult>>(state).InvokeContinuation();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the continuation with the appropriate captured context / scheduler.
|
||||
/// This assumes that if <see cref="_executionContext"/> is not null we're already
|
||||
/// running within that <see cref="ExecutionContext"/>.
|
||||
/// </summary>
|
||||
private void InvokeContinuation()
|
||||
{
|
||||
Debug.Assert(_continuation != null);
|
||||
|
||||
switch (_capturedContext)
|
||||
{
|
||||
case null:
|
||||
if (RunContinuationsAsynchronously)
|
||||
{
|
||||
if (_executionContext != null)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState));
|
||||
}
|
||||
else
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
#else
|
||||
ThreadPool.UnsafeQueueUserWorkItem(
|
||||
#endif
|
||||
WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_continuation!(_continuationState);
|
||||
}
|
||||
break;
|
||||
|
||||
case SynchronizationContext sc:
|
||||
sc.Post(s =>
|
||||
{
|
||||
var state = (Tuple<Action<object?>, object?>)s!;
|
||||
state.Item1(state.Item2);
|
||||
}, Tuple.Create(_continuation, _continuationState));
|
||||
break;
|
||||
|
||||
case TaskScheduler ts:
|
||||
Task.Factory.StartNew(_continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication
|
||||
{
|
||||
internal static readonly Action<object?> s_sentinel = CompletionSentinel;
|
||||
private static void CompletionSentinel(object? _) // named method to aid debugging
|
||||
{
|
||||
Debug.Fail("The sentinel delegate should never be invoked.");
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,9 @@
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal readonly struct Nothing // to express ValueTask via PooledState<Nothing>
|
||||
{
|
||||
public override string ToString() => nameof(Nothing);
|
||||
public override int GetHashCode() => 0;
|
||||
public override bool Equals(object? obj) => obj is Nothing;
|
||||
}
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal sealed class PooledState<T> : IValueTaskSource<T>, IValueTaskSource
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledState<T> Create(out short token)
|
||||
{
|
||||
var obj = Pool<PooledState<T>>.TryGet() ?? new PooledState<T>();
|
||||
token = obj._source.Version;
|
||||
return obj;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private PooledState() => Counters.PooledStateAllocated.Increment();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal bool IsValid(short token) => _source.Version == token;
|
||||
|
||||
private ManualResetValueTaskSourceCore<T> _source; // needs to be mutable
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetResult(short token)
|
||||
{
|
||||
// we only support getting the result once; doing this recycles the source and advances the token
|
||||
|
||||
lock (SyncLock) // we need to be really paranoid about cross-threading over changing the token
|
||||
{
|
||||
var status = _source.GetStatus(token); // do this *outside* the try/finally
|
||||
try // so that we don't increment the counter if someone asks for the wrong value
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case ValueTaskSourceStatus.Canceled:
|
||||
ThrowHelper.ThrowTaskCanceledException();
|
||||
break;
|
||||
case ValueTaskSourceStatus.Pending:
|
||||
Monitor.Wait(SyncLock);
|
||||
break;
|
||||
}
|
||||
return _source.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_source.Reset();
|
||||
if (_source.Version != TaskUtils.InitialTaskSourceVersion)
|
||||
{
|
||||
Pool<PooledState<T>>.TryPut(this);
|
||||
Counters.PooledStateRecycled.Increment();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void IValueTaskSource.GetResult(short token) => GetResult(token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void SignalResult(short token)
|
||||
{
|
||||
lock (SyncLock)
|
||||
{
|
||||
if (token == _source.Version && _source.GetStatus(token) != ValueTaskSourceStatus.Pending)
|
||||
{
|
||||
Monitor.Pulse(SyncLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
||||
=> _source.OnCompleted(continuation, state, token, flags);
|
||||
|
||||
private object SyncLock
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception error, short token)
|
||||
{
|
||||
if (token == _source.Version)
|
||||
{
|
||||
|
||||
switch (_source.GetStatus(token))
|
||||
{
|
||||
case ValueTaskSourceStatus.Pending:
|
||||
_source.SetException(error);
|
||||
// only need to signal if SetException didn't inline a handler
|
||||
if (token == _source.Version) SignalResult(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult(T result, short token)
|
||||
{
|
||||
if (token == _source.Version)
|
||||
{
|
||||
switch (_source.GetStatus(token))
|
||||
{
|
||||
case ValueTaskSourceStatus.Pending:
|
||||
_source.SetResult(result);
|
||||
// only need to signal if SetResult didn't inline a handler
|
||||
if (token == _source.Version) SignalResult(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled(short token)
|
||||
=> TrySetException(TaskUtils.SharedTaskCanceledException, token);
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal sealed class StateMachineBox<TStateMachine>
|
||||
#if PLAT_THREADPOOLWORKITEM
|
||||
: IThreadPoolWorkItem
|
||||
#endif
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
private readonly Action _execute;
|
||||
private TStateMachine _stateMachine;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private StateMachineBox()
|
||||
{
|
||||
_stateMachine = default!;
|
||||
_execute = Execute;
|
||||
Counters.StateMachineBoxAllocated.Increment();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static StateMachineBox<TStateMachine> Create(ref TStateMachine stateMachine)
|
||||
{
|
||||
var box = Pool<StateMachineBox<TStateMachine>>.TryGet() ?? new StateMachineBox<TStateMachine>();
|
||||
box._stateMachine = stateMachine;
|
||||
return box;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AwaitOnCompleted<TAwaiter>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
{
|
||||
var box = Create(ref stateMachine);
|
||||
if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter))
|
||||
{
|
||||
Yield(box, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
awaiter.OnCompleted(box._execute);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AwaitUnsafeOnCompleted<TAwaiter>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
{
|
||||
var box = Create(ref stateMachine);
|
||||
if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter))
|
||||
{
|
||||
Yield(box, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
awaiter.UnsafeOnCompleted(box._execute);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Yield(StateMachineBox<TStateMachine> box, bool flowContext)
|
||||
{
|
||||
// heavily inspired by YieldAwaitable.QueueContinuation
|
||||
|
||||
var syncContext = SynchronizationContext.Current;
|
||||
if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext))
|
||||
{
|
||||
syncContext.Post(s_SendOrPostCallback, box);
|
||||
}
|
||||
else
|
||||
{
|
||||
var taskScheduler = TaskScheduler.Current;
|
||||
if (!ReferenceEquals(taskScheduler, TaskScheduler.Default))
|
||||
{
|
||||
Task.Factory.StartNew(box._execute, default, TaskCreationOptions.PreferFairness, taskScheduler);
|
||||
}
|
||||
else if (flowContext)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(s_WaitCallback, box);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if PLAT_THREADPOOLWORKITEM
|
||||
ThreadPool.UnsafeQueueUserWorkItem(box, false);
|
||||
#elif NETSTANDARD1_3
|
||||
ThreadPool.QueueUserWorkItem(s_WaitCallback, box);
|
||||
#else
|
||||
ThreadPool.UnsafeQueueUserWorkItem(s_WaitCallback, box);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static readonly SendOrPostCallback s_SendOrPostCallback = state => ((StateMachineBox<TStateMachine>?)state!)?.Execute();
|
||||
static readonly WaitCallback s_WaitCallback = state => ((StateMachineBox<TStateMachine>?)state)?.Execute();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Execute()
|
||||
{
|
||||
// extract the state
|
||||
var tmp = _stateMachine;
|
||||
|
||||
// recycle the instance
|
||||
_stateMachine = default!;
|
||||
Pool<StateMachineBox<TStateMachine>>.TryPut(this);
|
||||
Counters.StateMachineBoxRecycled.Increment();
|
||||
|
||||
// progress the state machine
|
||||
tmp.MoveNext();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
// NET45 lacks some useful Task APIs; shim over them
|
||||
internal static class TaskUtils
|
||||
{
|
||||
internal static readonly short InitialTaskSourceVersion = new ManualResetValueTaskSourceCore<Nothing>().Version;
|
||||
|
||||
public static readonly TaskCanceledException SharedTaskCanceledException = new TaskCanceledException();
|
||||
#if NET45
|
||||
public static readonly Task CompletedTask = TaskFactory<Nothing>.FromResult(default);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task<T> FromException<T>(Exception exception)
|
||||
{
|
||||
var source = ValueTaskCompletionSource<T>.Create();
|
||||
source.TrySetException(exception);
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task FromException(Exception exception) => FromException<bool>(exception);
|
||||
#else
|
||||
public static readonly Task CompletedTask = Task.CompletedTask;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task<T> FromException<T>(Exception exception) => Task.FromException<T>(exception);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task FromException(Exception exception) => Task.FromException(exception);
|
||||
#endif
|
||||
|
||||
|
||||
internal static class TaskFactory<TResult>
|
||||
{
|
||||
// draws from AsyncMethodBuilder, but less boxing
|
||||
|
||||
public static readonly Task<TResult> Canceled = CreateCanceled();
|
||||
|
||||
static Task<TResult> CreateCanceled()
|
||||
{
|
||||
var source = ValueTaskCompletionSource<TResult>.Create();
|
||||
source.TrySetCanceled();
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
private static readonly TaskCache<TResult> _cache = (TaskCache<TResult>)CreateCacheForType();
|
||||
|
||||
private static object CreateCacheForType()
|
||||
{
|
||||
if (typeof(TResult) == typeof(Nothing)) return new NothingTaskCache();
|
||||
if (typeof(TResult) == typeof(int)) return new Int32TaskCache();
|
||||
if (typeof(TResult) == typeof(int?)) return new NullableInt32TaskCache();
|
||||
if (typeof(TResult) == typeof(bool)) return new BooleanTaskCache();
|
||||
if (typeof(TResult) == typeof(bool?)) return new NullableBooleanTaskCache();
|
||||
|
||||
Type underlyingType = Nullable.GetUnderlyingType(typeof(TResult)) ?? typeof(TResult);
|
||||
if (underlyingType == typeof(uint)
|
||||
|| underlyingType == typeof(byte)
|
||||
|| underlyingType == typeof(sbyte)
|
||||
|| underlyingType == typeof(char)
|
||||
|| underlyingType == typeof(decimal)
|
||||
|| underlyingType == typeof(long)
|
||||
|| underlyingType == typeof(ulong)
|
||||
|| underlyingType == typeof(short)
|
||||
|| underlyingType == typeof(ushort)
|
||||
|| underlyingType == typeof(float)
|
||||
|| underlyingType == typeof(double)
|
||||
|| underlyingType == typeof(IntPtr)
|
||||
|| underlyingType == typeof(UIntPtr)
|
||||
) return new DefaultEquatableTaskCache<TResult>();
|
||||
|
||||
if (typeof(TResult) == typeof(string)) return new StringTaskCache();
|
||||
#if !NETSTANDARD1_3
|
||||
if (!typeof(TResult).IsValueType) return new ObjectTaskCache<TResult>();
|
||||
#endif
|
||||
return new TaskCache<TResult>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task<TResult> FromResult(TResult result) => _cache.FromResult(result);
|
||||
}
|
||||
|
||||
class TaskCache<TResult>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal virtual Task<TResult> FromResult(TResult value) => Task.FromResult(value);
|
||||
}
|
||||
class NothingTaskCache : TaskCache<Nothing>
|
||||
{
|
||||
private static readonly Task<Nothing> s_Instance = Task.FromResult<Nothing>(default);
|
||||
internal override Task<Nothing> FromResult(Nothing value) => s_Instance;
|
||||
}
|
||||
class DefaultEquatableTaskCache<TResult> : TaskCache<TResult>
|
||||
{
|
||||
private static readonly Task<TResult> s_Default = Task.FromResult<TResult>(default!);
|
||||
private static readonly EqualityComparer<TResult> _comparer = EqualityComparer<TResult>.Default;
|
||||
internal override Task<TResult> FromResult(TResult value)
|
||||
=> _comparer.Equals(value, default!) ? s_Default : base.FromResult(value);
|
||||
}
|
||||
class ObjectTaskCache<TResult> : TaskCache<TResult>
|
||||
{
|
||||
private static readonly Task<TResult> s_Null = Task.FromResult<TResult>(default!);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<TResult> FromResult(TResult value)
|
||||
=> value == null ? s_Null : base.FromResult(value);
|
||||
}
|
||||
sealed class StringTaskCache : ObjectTaskCache<string>
|
||||
{
|
||||
private static readonly Task<string> s_Empty = Task.FromResult<string>("");
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<string> FromResult(string value)
|
||||
=> string.IsNullOrEmpty(value) ? s_Empty : base.FromResult(value);
|
||||
}
|
||||
sealed class BooleanTaskCache : TaskCache<bool>
|
||||
{
|
||||
static readonly Task<bool> s_True = Task.FromResult(true), s_False = Task.FromResult(false);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<bool> FromResult(bool value) => value ? s_True : s_False;
|
||||
}
|
||||
sealed class NullableBooleanTaskCache : TaskCache<bool?>
|
||||
{
|
||||
static readonly Task<bool?> s_True = Task.FromResult((bool?)true), s_False = Task.FromResult((bool?)false),
|
||||
s_Null = Task.FromResult((bool?)null);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<bool?> FromResult(bool? value) =>
|
||||
value.HasValue ? (value.GetValueOrDefault() ? s_True : s_False) : s_Null;
|
||||
}
|
||||
sealed class Int32TaskCache : TaskCache<int>
|
||||
{
|
||||
const int MIN_INC = -1, MAX_EXC = 11;
|
||||
static readonly Task<int>[] s_Known = CreateKnown();
|
||||
static Task<int>[] CreateKnown()
|
||||
{
|
||||
var arr = new Task<int>[MAX_EXC - MIN_INC];
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
arr[i] = Task.FromResult(i + MIN_INC);
|
||||
return arr;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<int> FromResult(int value)
|
||||
=> value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(value);
|
||||
}
|
||||
sealed class NullableInt32TaskCache : TaskCache<int?>
|
||||
{
|
||||
const int MIN_INC = -1, MAX_EXC = 11;
|
||||
static readonly Task<int?>[] s_Known = CreateKnown();
|
||||
static readonly Task<int?> s_Null = Task.FromResult((int?)null);
|
||||
static Task<int?>[] CreateKnown()
|
||||
{
|
||||
var arr = new Task<int?>[MAX_EXC - MIN_INC];
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
arr[i] = Task.FromResult((int?)(i + MIN_INC));
|
||||
return arr;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<int?> FromResult(int? nullable)
|
||||
{
|
||||
if (nullable.HasValue)
|
||||
{
|
||||
int value = nullable.GetValueOrDefault();
|
||||
return value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(nullable);
|
||||
}
|
||||
return s_Null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void ThrowInvalidOperationException(string? message = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message)) throw new InvalidOperationException();
|
||||
else throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static T ThrowInvalidOperationException<T>(string? message = null)
|
||||
{
|
||||
ThrowInvalidOperationException(message);
|
||||
return default!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static T ThrowNotSupportedException<T>() => throw new NotSupportedException();
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void ThrowTaskCanceledException() => throw new TaskCanceledException();
|
||||
}
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Like a ValueTaskCompletionSource, but the actual task will only become allocated
|
||||
/// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task
|
||||
/// </summary>
|
||||
public readonly struct LazyTaskCompletionSource : IDisposable
|
||||
{
|
||||
|
||||
private static LazyTaskCompletionSource _completed, _canceled;
|
||||
|
||||
/// <summary>
|
||||
/// A global LazyTaskCompletionSource that represents a completed operation
|
||||
/// </summary>
|
||||
public static LazyTaskCompletionSource CompletedTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _completed.IsValid ? _completed : _completed = new LazyTaskCompletionSource(LazyTaskState<Nothing>.CreateConstant(default));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A global LazyTaskCompletionSource that represents a cancelled operation
|
||||
/// </summary>
|
||||
public static LazyTaskCompletionSource CanceledTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource(LazyTaskState<Nothing>.CreateCanceled());
|
||||
}
|
||||
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is LazyTaskCompletionSource ltcs && _state == ltcs._state && _token == ltcs._token;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(LazyTaskCompletionSource);
|
||||
|
||||
private readonly LazyTaskState<Nothing> _state;
|
||||
private readonly short _token;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task associated with this instance
|
||||
/// </summary>
|
||||
public Task Task => _state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException<Task>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private LazyTaskCompletionSource(LazyTaskState<Nothing> state)
|
||||
{
|
||||
_state = state;
|
||||
_token = state.Version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance; this instance should be disposed when it is known to be unwanted
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskCompletionSource Create()
|
||||
=> new LazyTaskCompletionSource(LazyTaskState<Nothing>.Create());
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult() => _state?.TrySetResult(_token, default) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled(CancellationToken cancellationToken = default) => _state?.TrySetCanceled(_token, cancellationToken) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult()
|
||||
{
|
||||
if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetCanceled(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release all resources associated with this operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose() => _state?.Recycle(_token);
|
||||
|
||||
internal bool IsValid => _state?.IsValid(_token) == true;
|
||||
internal bool HasSource => _state?.HasSource == true;
|
||||
internal bool HasTask => _state?.HasTask == true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _state == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Like a ValueTaskCompletionSource<typeparamref name="T"/>, but the actual task will only become allocated
|
||||
/// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task
|
||||
/// </summary>
|
||||
public readonly struct LazyTaskCompletionSource<T> : IDisposable
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is LazyTaskCompletionSource<T> ltcs && _state == ltcs._state && _token == ltcs._token;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(LazyTaskCompletionSource);
|
||||
|
||||
private readonly LazyTaskState<T> _state;
|
||||
private readonly short _token;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task associated with this instance
|
||||
/// </summary>
|
||||
public Task<T> Task => (Task<T>)(_state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException<Task<T>>());
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private LazyTaskCompletionSource(LazyTaskState<T> state)
|
||||
{
|
||||
_state = state;
|
||||
_token = state.Version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance; this instance should be disposed when it is known to be unwanted
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskCompletionSource<T> Create()
|
||||
=> new LazyTaskCompletionSource<T>(LazyTaskState<T>.Create());
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance; this instance will never by recycled
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskCompletionSource<T> CreateConstant(T value)
|
||||
=> new LazyTaskCompletionSource<T>(LazyTaskState<T>.CreateConstant(value));
|
||||
|
||||
|
||||
private static LazyTaskCompletionSource<T> _canceled;
|
||||
|
||||
/// <summary>
|
||||
/// A global LazyTaskCompletionSource that represents a cancelled operation
|
||||
/// </summary>
|
||||
public static LazyTaskCompletionSource<T> CanceledTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource<T>(LazyTaskState<T>.CreateCanceled());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult(T result) => _state?.TrySetResult(_token, result) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled(CancellationToken cancellationToken = default)
|
||||
=> _state?.TrySetCanceled(_token, cancellationToken) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetCanceled(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release all resources associated with this operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose() => _state?.Recycle(_token);
|
||||
|
||||
internal bool IsValid => _state?.IsValid(_token) == true;
|
||||
internal bool HasSource => _state?.HasSource == true;
|
||||
internal bool HasTask => _state?.HasTask == true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _state == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace PooledAwait.MethodBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// This type is not intended for direct usage
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public struct FireAndForgetMethodBuilder
|
||||
{
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public override string ToString() => nameof(FireAndForgetMethodBuilder);
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FireAndForgetMethodBuilder Create() => default;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult() { }
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception) => FireAndForget.OnException(exception);
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public FireAndForget Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => default;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
=> StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
=> StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using SystemTask = System.Threading.Tasks.Task;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace PooledAwait.MethodBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// This type is not intended for direct usage
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public struct PooledTaskMethodBuilder
|
||||
{
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public override string ToString() => nameof(PooledTaskMethodBuilder);
|
||||
|
||||
private ValueTaskCompletionSource<Nothing> _source;
|
||||
private Exception _exception;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledTaskMethodBuilder Create() => default;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult()
|
||||
{
|
||||
_source.TrySetResult(default);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
_source.TrySetException(exception);
|
||||
_exception = exception;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureHasTask()
|
||||
{
|
||||
if (_source.IsNull) _source = ValueTaskCompletionSource<Nothing>.Create();
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public PooledTask Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
SystemTask task;
|
||||
if (!_source.IsNull) task = _source.Task;
|
||||
else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory<Nothing>.Canceled;
|
||||
else if (_exception != null) task = TaskUtils.FromException(_exception);
|
||||
else task = TaskUtils.CompletedTask;
|
||||
return new PooledTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace PooledAwait.MethodBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// This type is not intended for direct usage
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public struct PooledTaskMethodBuilder<T>
|
||||
{
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public override string ToString() => nameof(PooledTaskMethodBuilder);
|
||||
|
||||
private ValueTaskCompletionSource<T> _source;
|
||||
private Exception _exception;
|
||||
private T _result;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledTaskMethodBuilder<T> Create() => default;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
_source.TrySetResult(result);
|
||||
_result = result;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
_source.TrySetException(exception);
|
||||
_exception = exception;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public PooledTask<T> Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
Task<T> task;
|
||||
if (!_source.IsNull) task = _source.Task;
|
||||
else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory<T>.Canceled;
|
||||
else if (_exception != null) task = TaskUtils.FromException<T>(_exception);
|
||||
else task = TaskUtils.TaskFactory<T>.FromResult(_result);
|
||||
return new PooledTask<T>(task);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureHasTask()
|
||||
{
|
||||
if (_source.IsNull) _source = ValueTaskCompletionSource<T>.Create();
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user