@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 主线程执行调度
|
||||
/// </summary>
|
||||
/// 大概就是将 WPF 的 Dispatcher 传入
|
||||
public interface IMainThreadDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// 调度执行
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
Task InvokeAsync(Action action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using dotnetCampus.Configurations;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public interface IStartupContext
|
||||
{
|
||||
IAppConfigurator Configs { get; }
|
||||
Task<string> ReadCacheAsync(string key, string @default = "");
|
||||
Task WaitStartupTaskAsync(string startupKey);
|
||||
}
|
||||
}
|
||||
13
src/dotnetCampus.ApplicationStartupManager/IStartupLogger.cs
Normal file
13
src/dotnetCampus.ApplicationStartupManager/IStartupLogger.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public interface IStartupLogger
|
||||
{
|
||||
void RecordTime(string milestoneName);
|
||||
Task RecordDuration(string taskName, Func<Task<string>> task);
|
||||
void ReportResult(IReadOnlyList<IStartupTaskWrapper> wrappers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public interface IStartupManager
|
||||
{
|
||||
Task WaitStartupTaskAsync(string startupTaskKey);
|
||||
|
||||
StartupTask GetStartupTask<T>() where T : StartupTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public interface IStartupTaskWrapper
|
||||
{
|
||||
HashSet<string> FollowTasks { get; }
|
||||
HashSet<string> Dependencies { get; }
|
||||
string StartupTaskKey { get; }
|
||||
bool UIOnly { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public interface IStartupValueProvider<out T>
|
||||
{
|
||||
T ProvideValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
internal sealed class NullObjectStartup : StartupTask
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示启动流程的分类
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum StartupCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于启动流程快速上下架
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 用于所有模式都启动
|
||||
/// </summary>
|
||||
All = 1,
|
||||
}
|
||||
}
|
||||
37
src/dotnetCampus.ApplicationStartupManager/StartupContext.cs
Normal file
37
src/dotnetCampus.ApplicationStartupManager/StartupContext.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using dotnetCampus.Configurations;
|
||||
using dotnetCampus.Configurations.Core;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
internal class StartupContext : IStartupContext
|
||||
{
|
||||
public IStartupLogger Logger { get; }
|
||||
|
||||
public FileConfigurationRepo Configuration { get; }
|
||||
|
||||
public IAppConfigurator Configs { get; }
|
||||
|
||||
public Func<Exception, Task> FastFail { get; }
|
||||
|
||||
private readonly Func<string, Task> _waitStartupTaskAsync;
|
||||
|
||||
public Task<string> ReadCacheAsync(string key, string @default = "")
|
||||
{
|
||||
return Configuration.TryReadAsync(key, @default);
|
||||
}
|
||||
|
||||
Task IStartupContext.WaitStartupTaskAsync(string startupKey) => _waitStartupTaskAsync(startupKey);
|
||||
|
||||
public StartupContext(IStartupLogger logger, FileConfigurationRepo configuration,
|
||||
Func<Exception, Task> fastFailAction, Func<string, Task> waitStartupAsync)
|
||||
{
|
||||
Logger = logger;
|
||||
Configuration = configuration;
|
||||
_waitStartupTaskAsync = waitStartupAsync;
|
||||
Configs = configuration.CreateAppConfigurator();
|
||||
FastFail = fastFailAction ?? (exception => StartupTask.CompletedTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示启动任务的关键级别,用于在启动流程执行的过程中评估错误的影响范围。
|
||||
/// </summary>
|
||||
public enum StartupCriticalLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示没有评估过此模块的关键级别。
|
||||
/// 如果你无法评估此模块的关键级别,请保持默认值。
|
||||
/// </summary>
|
||||
Unset = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 表示这是一个扩展的启动任务,如果此任务初始化失败,软件不会有任何业务功能受到影响。
|
||||
/// 通常插件、性能和数据监控等模块会使用此级别。
|
||||
/// </summary>
|
||||
Extension,
|
||||
|
||||
/// <summary>
|
||||
/// 表示这是一个普通的启动任务,如果此任务初始化失败,软件的多数功能不会受到影响。
|
||||
/// 通常如果一个启动任务初始化的模块不被其他任何模块使用到,那么指定为此级别。
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// 表示这是一个关键启动任务,如果此任务初始化失败,软件将很难正常运行
|
||||
/// </summary>
|
||||
Critical,
|
||||
}
|
||||
}
|
||||
58
src/dotnetCampus.ApplicationStartupManager/StartupLogger.cs
Normal file
58
src/dotnetCampus.ApplicationStartupManager/StartupLogger.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public class StartupLogger : IStartupLogger
|
||||
{
|
||||
private readonly Stopwatch _mainWatch;
|
||||
|
||||
private readonly ConcurrentDictionary<string, (string name, long start, long elapsed)>
|
||||
_milestoneDictionary = new ConcurrentDictionary<string, (string, long, long)>();
|
||||
|
||||
public StartupLogger()
|
||||
{
|
||||
_mainWatch = new Stopwatch();
|
||||
_mainWatch.Start();
|
||||
}
|
||||
|
||||
public void RecordTime(string milestoneName)
|
||||
{
|
||||
var start = _milestoneDictionary.Count > 0
|
||||
? _milestoneDictionary.Max(x => x.Value.start + x.Value.elapsed)
|
||||
: 0;
|
||||
var end = _mainWatch.ElapsedTicks;
|
||||
_milestoneDictionary[milestoneName] =
|
||||
(Thread.CurrentThread.Name ?? Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture),
|
||||
start, end - start);
|
||||
}
|
||||
|
||||
public async Task RecordDuration(string taskName, Func<Task<string>> task)
|
||||
{
|
||||
var threadName = "null";
|
||||
var begin = _mainWatch.ElapsedTicks;
|
||||
|
||||
try
|
||||
{
|
||||
threadName = await task().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var end = _mainWatch.ElapsedTicks;
|
||||
var elapse = end - begin;
|
||||
_milestoneDictionary[taskName] = (threadName, begin, elapse);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReportResult(IReadOnlyList<IStartupTaskWrapper> wrappers)
|
||||
{
|
||||
// todo 还没有具体实现
|
||||
}
|
||||
}
|
||||
}
|
||||
443
src/dotnetCampus.ApplicationStartupManager/StartupManager.cs
Normal file
443
src/dotnetCampus.ApplicationStartupManager/StartupManager.cs
Normal file
@@ -0,0 +1,443 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using dotnetCampus.Configurations.Core;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public class StartupManager : IStartupManager
|
||||
{
|
||||
private readonly IMainThreadDispatcher _dispatcher;
|
||||
|
||||
/// <summary>
|
||||
/// Builder 模式所需状态:包含当前剩余需要管理的启动任务程序集。
|
||||
/// </summary>
|
||||
private readonly List<Assembly> _assembliesToBeManaged = new List<Assembly>();
|
||||
|
||||
/// <summary>
|
||||
/// Builder 模式所需状态:包含当前所有的关键启动任务。
|
||||
/// </summary>
|
||||
private readonly List<StartupTaskWrapper> _criticalTasks = new List<StartupTaskWrapper>();
|
||||
|
||||
/// <summary>
|
||||
/// Builder 模式所需状态:包含当前所有的关键启动任务。
|
||||
/// </summary>
|
||||
private readonly List<Action<StartupTaskBuilder>> _additionalBuilders = new List<Action<StartupTaskBuilder>>();
|
||||
|
||||
/// <summary>
|
||||
/// Builder 模式所需状态:用于决定只有哪一些启动任务才是有效的。
|
||||
/// </summary>
|
||||
private StartupCategory _selectingCategories = StartupCategory.All;
|
||||
|
||||
private readonly int _workerThreads;
|
||||
private readonly int _completionPortThreads;
|
||||
|
||||
internal ConcurrentDictionary<string, StartupTaskWrapper> StartupTaskWrappers { get; } =
|
||||
new ConcurrentDictionary<string, StartupTaskWrapper>();
|
||||
|
||||
private List<StartupTaskWrapper> Graph { get; set; }
|
||||
|
||||
private StartupContext Context { get; }
|
||||
|
||||
private IStartupLogger Logger => Context.Logger;
|
||||
|
||||
public StartupManager(IStartupLogger logger, FileConfigurationRepo configurationRepo,
|
||||
Func<Exception, Task> fastFailAction, IMainThreadDispatcher dispatcher, bool shouldSetThreadPool = true)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (configurationRepo is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurationRepo));
|
||||
}
|
||||
|
||||
_dispatcher = dispatcher;
|
||||
|
||||
if (shouldSetThreadPool)
|
||||
{
|
||||
ThreadPool.GetMinThreads(out _workerThreads, out _completionPortThreads);
|
||||
//启动期间存在大量的线程池调用(包含IO操作),而创建的多数线程在等待 IO 时都是不会被调度的
|
||||
//设置更多的初始化线程数可以减少启动期间的线程调度等待
|
||||
ThreadPool.SetMinThreads(Math.Max(_workerThreads, 16), Math.Max(_completionPortThreads, 16));
|
||||
}
|
||||
|
||||
Context = new StartupContext(logger, configurationRepo,
|
||||
fastFailAction, WaitStartupTaskAsync);
|
||||
|
||||
Logger.RecordTime("ManagerInitialized");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置被 <see cref="StartupManager"/> 管理的程序集。
|
||||
/// 只有被管理的程序集中的启动信息、依赖注入信息才会被执行。
|
||||
/// </summary>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <returns></returns>
|
||||
public StartupManager ConfigAssemblies(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
// 可能的限制尚未完成:
|
||||
// 1. Run 之后不能再调用此方法(适用于固定的程序集应用);
|
||||
// 2. 可以多次 Config 然后多次 Run(适用于动态加载的插件程序集)。
|
||||
_assembliesToBeManaged.AddRange(assemblies);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在启动流程中定义一组关键的启动节点。使用此方法定义的关键启动节点将按顺序前后依次依赖。
|
||||
/// 例如传入 A、B、C、D 四个关键启动节点,那么 A - B - C - D 将依次执行,其他任务将插入其中。
|
||||
/// </summary>
|
||||
/// <param name="criticalNodeKeys">关键启动节点的名称。</param>
|
||||
/// <returns><see cref="StartupManager"/> 实例自身,用于使用重建者模式创建启动流程管理器。</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 通常情况下你可以编写常规的 StartupTask 来添加一个关键节点,只要这个节点被众多节点声明了依赖,那么它就能视为一个关键节点。
|
||||
/// 使用此方法可以添加一些虚拟的,实际上不会执行任何启动任务的关键启动节点,用于汇总其他模块的依赖关系。
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public StartupManager UseCriticalNodes(params string[] criticalNodeKeys)
|
||||
{
|
||||
if (criticalNodeKeys == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(criticalNodeKeys));
|
||||
}
|
||||
|
||||
foreach (var wrapper in EnumerateCreate())
|
||||
{
|
||||
if (_criticalTasks.Find(x => x.StartupTaskKey == wrapper.StartupTaskKey) != null)
|
||||
{
|
||||
throw new ArgumentException($@"不能重复添加 {wrapper.StartupTaskKey} 的关键启动任务。", nameof(criticalNodeKeys));
|
||||
}
|
||||
|
||||
_criticalTasks.Add(wrapper);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
IEnumerable<StartupTaskWrapper> EnumerateCreate()
|
||||
{
|
||||
for (var i = 0; i < criticalNodeKeys.Length; i++)
|
||||
{
|
||||
var key = criticalNodeKeys[i];
|
||||
var current = GetStartupTaskWrapper(key);
|
||||
current.StartupTask = new NullObjectStartup();
|
||||
current.Categories = StartupCategory.All;
|
||||
|
||||
if (i - 1 >= 0)
|
||||
{
|
||||
current.Dependencies.Add(criticalNodeKeys[i - 1]);
|
||||
}
|
||||
|
||||
if (i + 1 <= criticalNodeKeys.Length - 1)
|
||||
{
|
||||
current.FollowTasks.Add(criticalNodeKeys[i + 1]);
|
||||
}
|
||||
|
||||
yield return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向启动流程中再额外添加一个关键启动节点。
|
||||
/// </summary>
|
||||
/// <param name="nodeName">关键节点的名称。</param>
|
||||
/// <param name="beforeTasks">关键节点的前置节点。</param>
|
||||
/// <param name="afterTasks">关键节点的后置节点。</param>
|
||||
/// <returns><see cref="StartupManager"/> 实例自身,用于使用重建者模式创建启动流程管理器。</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 通常情况下你可以编写常规的 StartupTask 来添加一个关键节点,只要这个节点被众多节点声明了依赖,那么它就能视为一个关键节点。
|
||||
/// 但是,使用 StartupTask 的方式生成的关键节点是静态的,在一个版本的程序集已完成之后便不可再被修改。
|
||||
/// 你可以使用此方法创建动态的关键启动节点。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 例如,你需要根据不同的启动条件决定不同的启动顺序,那么你可能需要使用此方法动态生成关键节点。
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public StartupManager AddCriticalNodes(string nodeName, string beforeTasks = null, string afterTasks = null)
|
||||
{
|
||||
var wrapper = GetStartupTaskWrapper(nodeName);
|
||||
wrapper.StartupTask = new NullObjectStartup();
|
||||
wrapper.Categories = StartupCategory.All;
|
||||
|
||||
if (beforeTasks?.Split(new[] {";"}, StringSplitOptions.RemoveEmptyEntries) is string[] before)
|
||||
{
|
||||
foreach (var b in before)
|
||||
{
|
||||
wrapper.FollowTasks.Add(b);
|
||||
GetStartupTaskWrapper(b).Dependencies.Add(nodeName);
|
||||
}
|
||||
}
|
||||
|
||||
if (afterTasks?.Split(new[] {";"}, StringSplitOptions.RemoveEmptyEntries) is string[] after)
|
||||
{
|
||||
foreach (var a in after)
|
||||
{
|
||||
wrapper.Dependencies.Add(a);
|
||||
GetStartupTaskWrapper(a).FollowTasks.Add(nodeName);
|
||||
}
|
||||
}
|
||||
|
||||
_criticalTasks.Add(wrapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StartupManager SelectNodes(StartupCategory categories)
|
||||
{
|
||||
_selectingCategories = categories;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StartupManager ForStartupTasksOfCategory(StartupCategory category,
|
||||
Action<StartupTaskBuilder> taskBuilder)
|
||||
{
|
||||
_additionalBuilders.Add(builder =>
|
||||
{
|
||||
if (builder.Categories == category)
|
||||
{
|
||||
taskBuilder(builder);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public async void Run()
|
||||
{
|
||||
if (Graph == null)
|
||||
{
|
||||
Graph = BuildStartupGraph();
|
||||
Logger.RecordTime("GraphBuilded");
|
||||
}
|
||||
|
||||
var dispatcher = _dispatcher;
|
||||
foreach (var wrapper in Graph)
|
||||
{
|
||||
var startupTasks = wrapper.Dependencies.Select(s => GetStartupTaskWrapper(s).StartupTask);
|
||||
if (wrapper.UIOnly)
|
||||
{
|
||||
await dispatcher.InvokeAsync(() => wrapper.ExecuteTask(startupTasks, Context));
|
||||
}
|
||||
else
|
||||
{
|
||||
wrapper.ExecuteTask(startupTasks, Context);
|
||||
}
|
||||
}
|
||||
|
||||
await Graph.Last().StartupTask.TaskResult;
|
||||
|
||||
Logger.RecordTime("AllStartupTasksCompleted");
|
||||
|
||||
Debug.WriteLine(Logger);
|
||||
#pragma warning disable CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
|
||||
dispatcher.InvokeAsync(() =>
|
||||
Logger.ReportResult(Graph.OfType<IStartupTaskWrapper>().ToList()));
|
||||
#pragma warning restore CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
|
||||
|
||||
ThreadPool.SetMinThreads(Math.Max(_workerThreads, 8), Math.Max(_completionPortThreads, 8));
|
||||
}
|
||||
|
||||
private List<StartupTaskWrapper> BuildStartupGraph()
|
||||
{
|
||||
var wrappers = _criticalTasks.ToList();
|
||||
foreach (var wrapper in wrappers)
|
||||
{
|
||||
wrapper.Categories &= _selectingCategories;
|
||||
}
|
||||
|
||||
var taskMetadataList = ExportStartupTasks(_assembliesToBeManaged, _selectingCategories).ToList();
|
||||
|
||||
foreach (var meta in taskMetadataList)
|
||||
{
|
||||
var wrapper = GetStartupTaskWrapper(meta.Key);
|
||||
wrapper.UIOnly = meta.Scheduler == StartupScheduler.UIOnly;
|
||||
wrapper.Categories = meta.Categories;
|
||||
wrapper.CriticalLevel = meta.CriticalLevel;
|
||||
wrapper.StartupTask = meta.Instance;
|
||||
wrapper.StartupTask.Manager = this;
|
||||
wrappers.Add(wrapper);
|
||||
}
|
||||
|
||||
foreach (var wrapper in wrappers)
|
||||
{
|
||||
// 对于预设的启动任务,在此处先执行构造。
|
||||
var taskBuilder = new StartupTaskBuilder(wrapper, AddDependencies, AddFollowTasks);
|
||||
foreach (var builder in _additionalBuilders)
|
||||
{
|
||||
builder(taskBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var meta in taskMetadataList)
|
||||
{
|
||||
var wrapper = GetStartupTaskWrapper(meta.Key);
|
||||
|
||||
if (meta.AfterTasks != null)
|
||||
{
|
||||
AddDependencies(wrapper, meta.AfterTasks);
|
||||
}
|
||||
|
||||
if (meta.BeforeTasks != null)
|
||||
{
|
||||
AddFollowTasks(wrapper, meta.BeforeTasks);
|
||||
}
|
||||
}
|
||||
|
||||
return DFSGraph(wrappers);
|
||||
|
||||
IEnumerable<StartupTaskMetadata> ExportStartupTasks(
|
||||
IEnumerable<Assembly> assemblies, StartupCategory categories)
|
||||
{
|
||||
// todo 高性能的预编译框架接入
|
||||
//foreach (var meta in AssemblyMetadataExporter.ExportStartupTasks(assemblies))
|
||||
//{
|
||||
// meta.Categories &= categories;
|
||||
// if (meta.Categories != StartupCategory.None)
|
||||
// {
|
||||
// yield return meta;
|
||||
// }
|
||||
//}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
void AddDependencies(StartupTaskWrapper wrapper, string afterTasks)
|
||||
{
|
||||
foreach (var task in afterTasks.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(CompatibleTaskName))
|
||||
{
|
||||
if (!taskMetadataList.Exists(metadata => metadata.Key == task)
|
||||
&& !wrappers.Exists(taskWrapper => taskWrapper.StartupTaskKey == task))
|
||||
{
|
||||
throw new InvalidOperationException($"该启动流程{wrapper.StartupTaskKey}的依赖项{task}未加入到该Category");
|
||||
}
|
||||
|
||||
var dependentWrappers = GetStartupTaskWrapper(task);
|
||||
if (dependentWrappers.Categories.HasFlag(wrapper.Categories))
|
||||
{
|
||||
dependentWrappers.FollowTasks.Add(wrapper.StartupTaskKey);
|
||||
wrapper.Dependencies.Add(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddFollowTasks(StartupTaskWrapper wrapper, string beforeTasks)
|
||||
{
|
||||
foreach (var task in beforeTasks.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(CompatibleTaskName))
|
||||
{
|
||||
if (!taskMetadataList.Exists(metadata => metadata.Key == task)
|
||||
&& !wrappers.Exists(taskWrapper => taskWrapper.StartupTaskKey == task))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var followedWrappers = GetStartupTaskWrapper(task);
|
||||
if (wrapper.Categories.HasFlag(followedWrappers.Categories))
|
||||
{
|
||||
wrapper.FollowTasks.Add(task);
|
||||
followedWrappers.Dependencies.Add(wrapper.StartupTaskKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string CompatibleTaskName(string task)
|
||||
{
|
||||
const string startupName = "Startup";
|
||||
|
||||
if (task.EndsWith(startupName))
|
||||
{
|
||||
return task.Remove(task.Length - startupName.Length, startupName.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private List<StartupTaskWrapper> DFSGraph(List<StartupTaskWrapper> wrappers)
|
||||
{
|
||||
var time = 0;
|
||||
wrappers.ForEach(wrapper =>
|
||||
{
|
||||
if (wrapper.IsVisited == VisitState.Visiting)
|
||||
{
|
||||
throw new InvalidOperationException("深度优先遍历出错");
|
||||
}
|
||||
|
||||
if (wrapper.IsVisited == VisitState.Unvisited)
|
||||
{
|
||||
time = DFSVisit(wrapper, time);
|
||||
}
|
||||
});
|
||||
var topologicalList = wrappers.OrderByDescending(wrapper => wrapper.VisitedFinishTime).ToList();
|
||||
return topologicalList;
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private int DFSVisit(StartupTaskWrapper visitWrapper, int startTime)
|
||||
{
|
||||
var time = startTime + 1;
|
||||
visitWrapper.IsVisited = VisitState.Visiting;
|
||||
|
||||
foreach (var s in visitWrapper.FollowTasks)
|
||||
{
|
||||
var wrapper = GetStartupTaskWrapper(s);
|
||||
if (wrapper.IsVisited == VisitState.Visiting)
|
||||
{
|
||||
throw new InvalidOperationException($"启动序列图存在环:{wrapper.StartupTaskKey}");
|
||||
}
|
||||
|
||||
if (wrapper.IsVisited == VisitState.Unvisited)
|
||||
{
|
||||
time = DFSVisit(wrapper, time);
|
||||
}
|
||||
}
|
||||
|
||||
visitWrapper.VisitedFinishTime = time + 1;
|
||||
visitWrapper.IsVisited = VisitState.Visited;
|
||||
return visitWrapper.VisitedFinishTime;
|
||||
}
|
||||
|
||||
internal StartupTaskWrapper GetStartupTaskWrapper(string startupTaskKey)
|
||||
{
|
||||
if (StartupTaskWrappers.TryGetValue(startupTaskKey, out var wrapper))
|
||||
{
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
wrapper = new StartupTaskWrapper(startupTaskKey);
|
||||
if (StartupTaskWrappers.TryAdd(startupTaskKey, wrapper))
|
||||
{
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
if (StartupTaskWrappers.TryGetValue(startupTaskKey, out wrapper))
|
||||
{
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"{startupTaskKey}既无法添加至字典,也无法从字典获取对应值");
|
||||
}
|
||||
|
||||
public Task WaitStartupTaskAsync(string startupTaskKey)
|
||||
=> GetStartupTaskWrapper(startupTaskKey).StartupTask.TaskResult;
|
||||
|
||||
StartupTask IStartupManager.GetStartupTask<T>()
|
||||
=> GetStartupTaskWrapper(StartupTypeToKey(typeof(T))).StartupTask;
|
||||
|
||||
private static string StartupTypeToKey(Type type)
|
||||
=> type.Name.Remove(type.Name.Length - "startup".Length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示启动任务将如何安排执行。
|
||||
/// </summary>
|
||||
public enum StartupScheduler
|
||||
{
|
||||
/// <summary>
|
||||
/// 允许在所有线程执行。
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// 要求必须在主 UI 线程执行。
|
||||
/// </summary>
|
||||
UIOnly,
|
||||
}
|
||||
}
|
||||
59
src/dotnetCampus.ApplicationStartupManager/StartupTask.cs
Normal file
59
src/dotnetCampus.ApplicationStartupManager/StartupTask.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public abstract class StartupTask
|
||||
{
|
||||
// 由于我们都在编译期间收集 Attribute 了,当然也能收集使用方到底重写了哪个 Run。
|
||||
// 这里传入的 isUIOnly 就是编译期间收集的那个属性。
|
||||
public async Task<string> JoinAsync(IStartupContext context, bool isUIOnly)
|
||||
{
|
||||
// 决定执行 Run 还是 RunAsync。
|
||||
// 进行性能统计,并报告结果。
|
||||
if (!isUIOnly)
|
||||
{
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
await RunAsync(context);
|
||||
CompletedSource.SetResult(null);
|
||||
return Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await RunAsync(context);
|
||||
CompletedSource.SetResult(null);
|
||||
return Thread.CurrentThread.Name ??
|
||||
Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Task RunAsync(IStartupContext context)
|
||||
{
|
||||
return CompletedTask;
|
||||
}
|
||||
|
||||
protected internal static Task CompletedTask =>
|
||||
#if NETFRAMEWORK
|
||||
CompletedCommonTask;
|
||||
private static readonly Task CompletedCommonTask = Task.FromResult(true);
|
||||
#else
|
||||
Task.CompletedTask;
|
||||
#endif
|
||||
|
||||
public Task TaskResult => CompletedSource.Task;
|
||||
|
||||
private TaskCompletionSource<object> CompletedSource { get; } = new TaskCompletionSource<object>();
|
||||
|
||||
internal IStartupManager Manager { get; set; }
|
||||
|
||||
protected TValue FetchValue<TStartup, TValue>() where TStartup : StartupTask, IStartupValueProvider<TValue>
|
||||
{
|
||||
var task = Manager.GetStartupTask<TStartup>();
|
||||
var v = (IStartupValueProvider<TValue>)task;
|
||||
return v.ProvideValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class StartupTaskAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 此启动任务的任务必须在指定的其他启动任务之前完成。可以使用 “;” 分隔符指定多个启动任务。
|
||||
/// </summary>
|
||||
public string BeforeTasks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 效果上等同于 <see cref="BeforeTasks"/> 属性,此属性只是为了开发方便而已
|
||||
/// </summary>
|
||||
public string[] BeforeTaskList
|
||||
{
|
||||
set
|
||||
{
|
||||
BeforeTasks = StartupTaskHelper.BuildTasks(value);
|
||||
}
|
||||
get
|
||||
{
|
||||
return BeforeTasks.Split(';');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 此启动任务的任务将在指定的其他启动任务之后开始执行。可以使用 “;” 分隔符指定多个启动任务。
|
||||
/// </summary>
|
||||
public string AfterTasks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 效果上等同于 <see cref="AfterTasks"/> 属性,此属性只是为了开发方便而已
|
||||
/// </summary>
|
||||
public string[] AfterTaskList
|
||||
{
|
||||
set
|
||||
{
|
||||
AfterTasks = StartupTaskHelper.BuildTasks(value);
|
||||
}
|
||||
get
|
||||
{
|
||||
return AfterTasks.Split(';');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定启动任务任务的上下文。
|
||||
/// </summary>
|
||||
public StartupScheduler Scheduler { get; set; } = StartupScheduler.Default;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置此启动任务的关键级别。
|
||||
/// </summary>
|
||||
public StartupCriticalLevel CriticalLevel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
public class StartupTaskBuilder
|
||||
{
|
||||
private readonly StartupTaskWrapper _wrapper;
|
||||
private readonly Action<StartupTaskWrapper, string> _addDependenciesAction;
|
||||
private readonly Action<StartupTaskWrapper, string> _addFollowTasksAction;
|
||||
|
||||
internal StartupTaskBuilder(StartupTaskWrapper wrapper,
|
||||
Action<StartupTaskWrapper, string> addDependenciesAction,
|
||||
Action<StartupTaskWrapper, string> addFollowTasksAction)
|
||||
{
|
||||
_wrapper = wrapper;
|
||||
_addDependenciesAction = addDependenciesAction;
|
||||
_addFollowTasksAction = addFollowTasksAction;
|
||||
}
|
||||
|
||||
public StartupCategory Categories
|
||||
{
|
||||
get => _wrapper.Categories;
|
||||
set => _wrapper.Categories = value;
|
||||
}
|
||||
|
||||
public StartupTaskBuilder AddDependencies(string afterTasks)
|
||||
{
|
||||
_addDependenciesAction(_wrapper, afterTasks);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StartupTaskBuilder AddFollowTasks(string beforeTasks)
|
||||
{
|
||||
_addFollowTasksAction(_wrapper, beforeTasks);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
static class StartupTaskHelper
|
||||
{
|
||||
public static string BuildTasks(params string[] taskList)
|
||||
{
|
||||
if (taskList.Length > 1)
|
||||
{
|
||||
return string.Join(";", taskList);
|
||||
}
|
||||
|
||||
if (taskList.Length == 1)
|
||||
{
|
||||
return taskList[0];
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录 <typeparamref name="TStartupTask"/> 类型中标记的从 <see cref="StartupTaskAttribute"/> 中统一收集元数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TStartupTask"></typeparam>
|
||||
public class StartupTaskMetadata<TStartupTask> : StartupTaskMetadata where TStartupTask : StartupTask, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建包含 <typeparamref name="TStartupTask"/> 元数据的 <see cref="StartupTaskMetadata{TStartupTask}"/> 的新实例。
|
||||
/// </summary>
|
||||
public StartupTaskMetadata() : base(typeof(TStartupTask).Name.Replace("Startup", ""), () => new TStartupTask())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录 <see cref="StartupTask"/> 类型中标记的从 <see cref="StartupTaskAttribute"/> 中统一收集元数据。
|
||||
/// </summary>
|
||||
public class StartupTaskMetadata
|
||||
{
|
||||
private readonly Lazy<StartupTask> _taskLazy;
|
||||
private string _afterTasks;
|
||||
private string _beforeTasks;
|
||||
|
||||
/// <summary>
|
||||
/// 创建 <see cref="StartupTaskMetadata"/> 的新实例。
|
||||
/// </summary>
|
||||
/// <param name="key">表示此 <see cref="StartupTask"/> 的唯一标识符。</param>
|
||||
/// <param name="creator">此 <see cref="StartupTask"/> 实例的创建方法。</param>
|
||||
public StartupTaskMetadata( string key, Func<StartupTask> creator)
|
||||
{
|
||||
Key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
_taskLazy = new Lazy<StartupTask>(
|
||||
creator ?? throw new ArgumentNullException(nameof(creator)),
|
||||
LazyThreadSafetyMode.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 <see cref="StartupTask"/> 的唯一标识符。
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取<see cref="StartupTaskAttribute.Categories"/> 的值,
|
||||
/// </summary>
|
||||
public StartupCategory Categories { get; set; } = StartupCategory.All;
|
||||
|
||||
/// <summary>
|
||||
/// 获取 <see cref="StartupTaskAttribute.BeforeTasks"/> 的值,如果没有标记,则为 null。
|
||||
/// </summary>
|
||||
public string BeforeTasks
|
||||
{
|
||||
get => _beforeTasks;
|
||||
set => _beforeTasks = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 <see cref="StartupTaskAttribute.AfterTasks"/> 的值,如果没有标记,则为 null。
|
||||
/// </summary>
|
||||
public string AfterTasks
|
||||
{
|
||||
get => _afterTasks;
|
||||
set => _afterTasks = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 <see cref="StartupTaskAttribute.Scheduler"/> 的值,如果没有标记,则为 <see cref="StartupScheduler.Default"/>。
|
||||
/// </summary>
|
||||
public StartupScheduler Scheduler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 根据从元数据中收集到的创建 <see cref="StartupTask"/> 的方法获取或创建 <see cref="StartupTask"/> 的实例。
|
||||
/// </summary>
|
||||
public StartupTask Instance => _taskLazy.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 获取 <see cref="StartupTaskAttribute.CriticalLevel"/> 的值,如果没有获取或设置此启动任务的关键级别,则为 <see cref="StartupCriticalLevel.Unset"/>。
|
||||
/// </summary>
|
||||
public StartupCriticalLevel CriticalLevel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
[DebuggerDisplay("{StartupTaskKey}:{IsVisited},{VisitedFinishTime}")]
|
||||
internal class StartupTaskWrapper : IStartupTaskWrapper
|
||||
{
|
||||
public HashSet<string> FollowTasks { get; private set; } = new HashSet<string>();
|
||||
public HashSet<string> Dependencies { get; private set; } = new HashSet<string>();
|
||||
|
||||
public string StartupTaskKey { get; }
|
||||
|
||||
internal VisitState IsVisited { get; set; } = VisitState.Unvisited;
|
||||
|
||||
internal int VisitedFinishTime { get; set; } = 0;
|
||||
|
||||
public StartupCategory Categories { get; internal set; } = StartupCategory.All;
|
||||
|
||||
public StartupTask StartupTask { get; internal set; }
|
||||
public bool UIOnly { get; internal set; }
|
||||
public StartupCriticalLevel CriticalLevel { get; set; }
|
||||
|
||||
public StartupTaskWrapper(string startupTaskKey)
|
||||
{
|
||||
StartupTaskKey = startupTaskKey;
|
||||
}
|
||||
|
||||
public async void ExecuteTask(IEnumerable<StartupTask> dependencies, StartupContext context)
|
||||
{
|
||||
await Task.WhenAll(dependencies.Select(task => task.TaskResult));
|
||||
#pragma warning disable CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
|
||||
context.Logger.RecordDuration(StartupTaskKey,
|
||||
async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CriticalLevel == StartupCriticalLevel.Critical)
|
||||
{
|
||||
//todo Tracer.Info($"[Startup]关键节点:{StartupTaskKey}开始执行");
|
||||
}
|
||||
|
||||
var result = await StartupTask.JoinAsync(context, UIOnly);
|
||||
|
||||
if (CriticalLevel == StartupCriticalLevel.Critical)
|
||||
{
|
||||
//todo Tracer.Info($"[Startup]关键节点:{StartupTaskKey}执行完成");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (CriticalLevel == StartupCriticalLevel.Critical)
|
||||
{
|
||||
Trace.WriteLine(ex.ToString());
|
||||
await context.FastFail(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
//todo Tracer.Error(ex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 由于目前正处于启动期间,所以日志模块可能并未真正完成初始化。
|
||||
// 实际上日志模块一定初始化完毕了,因为日志之前的启动异常都会进入上面的 Critical 分支。
|
||||
}
|
||||
|
||||
//todo Trace.WriteLine(ex.ToString());
|
||||
#if DEBUG
|
||||
// 启动过程中非Critical级别的启动项出现异常,虽然不影响启动,但也应需要修复
|
||||
Debugger.Break();
|
||||
#endif
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
});
|
||||
#pragma warning restore CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/dotnetCampus.ApplicationStartupManager/VisitState.cs
Normal file
9
src/dotnetCampus.ApplicationStartupManager/VisitState.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace dotnetCampus.ApplicationStartupManager
|
||||
{
|
||||
internal enum VisitState
|
||||
{
|
||||
Unvisited,
|
||||
Visiting,
|
||||
Visited,
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dotnetCampus.Configurations" Version="1.6.9" />
|
||||
<PackageReference Include="dotnetCampus.SourceYard" Version="0.1.19369-alpha">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0"></PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user