├── CHANGELOG.md
├── demo
└── WPFDemo
│ ├── WPFDemo.App
│ ├── Properties
│ │ ├── launchSettings.json
│ │ └── AssemblyInfo.cs
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── StartupTaskFramework
│ │ └── MainThreadDispatcher.cs
│ ├── MainWindow.xaml
│ ├── AssemblyInfo.cs
│ ├── MainWindow.xaml.cs
│ ├── Startup
│ │ ├── MainWindowStartup.cs
│ │ └── BusinessStartup.cs
│ ├── WPFDemo.App.csproj
│ └── Program.cs
│ ├── WPFDemo.Api
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── CommandLines
│ │ └── Options.cs
│ ├── Startup
│ │ ├── OptionStartup.cs
│ │ └── Foo1Startup.cs
│ ├── StartupTaskFramework
│ │ ├── StartupTask.cs
│ │ ├── StartupLogger.cs
│ │ ├── StartupManager.cs
│ │ ├── StartupNodes.cs
│ │ ├── AssemblyMetadataExporter.cs
│ │ └── StartupContext.cs
│ └── WPFDemo.Api.csproj
│ └── WPFDemo.Lib1
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── Startup
│ ├── LibStartup.cs
│ ├── Foo2Startup.cs
│ └── Foo3Startup.cs
│ └── WPFDemo.Lib1.csproj
├── src
└── dotnetCampus.ApplicationStartupManager
│ ├── VisitState.cs
│ ├── NullObjectStartup.cs
│ ├── StartupScheduler.cs
│ ├── IStartupValueProvider.cs
│ ├── StartupCategory.cs
│ ├── IStartupContext.cs
│ ├── IMainThreadDispatcher.cs
│ ├── StartupTaskHelper.cs
│ ├── IStartupManager.cs
│ ├── IStartupTaskWrapper.cs
│ ├── StartupTaskMetadata.T.cs
│ ├── StartupCriticalLevel.cs
│ ├── IStartupLogger.cs
│ ├── StartupContext.cs
│ ├── StartupTaskBuilder.cs
│ ├── StartupTaskAttribute.cs
│ ├── StartupTaskMetadata.cs
│ ├── dotnetCampus.ApplicationStartupManager.csproj
│ ├── StartupLogger.cs
│ ├── StartupTaskWrapper.cs
│ ├── StartupTaskBase.cs
│ └── StartupManagerBase.cs
├── .github
└── workflows
│ ├── dotnet-build.yml
│ ├── nuget-tag-publish.yml
│ └── dotnet-format.yml
├── README.md
├── Directory.Build.props
├── .gitattributes
├── dotnetCampus.ApplicationStartupManager.sln
├── .editorconfig
└── .gitignore
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # dotnetCampus.ApplicationStartupManager
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "WPFDemo.App": {
4 | "commandName": "Project",
5 | "commandLineArgs": "-Name ApplicationStartupManager"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/VisitState.cs:
--------------------------------------------------------------------------------
1 | namespace dotnetCampus.ApplicationStartupManager
2 | {
3 | internal enum VisitState
4 | {
5 | Unvisited,
6 | Visiting,
7 | Visited,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 | using dotnetCampus.Telescope;
3 |
4 | using WPFDemo.Api.StartupTaskFramework;
5 |
6 | [assembly: MarkExport(typeof(StartupTask), typeof(StartupTaskAttribute))]
7 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 | using dotnetCampus.Telescope;
3 |
4 | using WPFDemo.Api.StartupTaskFramework;
5 |
6 | [assembly: MarkExport(typeof(StartupTask), typeof(StartupTaskAttribute))]
7 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Lib1/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 | using dotnetCampus.Telescope;
3 |
4 | using WPFDemo.Api.StartupTaskFramework;
5 |
6 | [assembly: MarkExport(typeof(StartupTask), typeof(StartupTaskAttribute))]
7 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/NullObjectStartup.cs:
--------------------------------------------------------------------------------
1 | namespace dotnetCampus.ApplicationStartupManager
2 | {
3 | ///
4 | /// 一个表示空白的启动项
5 | ///
6 | internal sealed class NullObjectStartup : StartupTaskBase
7 | {
8 |
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/CommandLines/Options.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.Cli;
2 |
3 | namespace WPFDemo.Api.CommandLines
4 | {
5 | ///
6 | /// 启动参数
7 | ///
8 | public class Options
9 | {
10 | [Option("Name")]
11 | public string? Name { set; get; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace WPFDemo.App
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupScheduler.cs:
--------------------------------------------------------------------------------
1 | namespace dotnetCampus.ApplicationStartupManager
2 | {
3 | ///
4 | /// 表示启动任务将如何安排执行。
5 | ///
6 | public enum StartupScheduler
7 | {
8 | ///
9 | /// 允许在所有线程执行。
10 | ///
11 | Default,
12 |
13 | ///
14 | /// 要求必须在主 UI 线程执行。
15 | ///
16 | UIOnly,
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/IStartupValueProvider.cs:
--------------------------------------------------------------------------------
1 | namespace dotnetCampus.ApplicationStartupManager
2 | {
3 | ///
4 | /// 用于启动项里的值提供器,用于多个启动项之间传递参数
5 | ///
6 | ///
7 | public interface IStartupValueProvider
8 | {
9 | ///
10 | /// 获取启动项提供的值
11 | ///
12 | ///
13 | T ProvideValue();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupCategory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace dotnetCampus.ApplicationStartupManager
4 | {
5 | ///
6 | /// 表示启动流程的分类
7 | ///
8 | [Flags]
9 | public enum StartupCategory
10 | {
11 | ///
12 | /// 用于启动流程快速上下架
13 | ///
14 | None = 0,
15 |
16 | ///
17 | /// 用于所有模式都启动
18 | ///
19 | All = 1,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Lib1/Startup/LibStartup.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 | using WPFDemo.Api.StartupTaskFramework;
3 |
4 | namespace WPFDemo.Lib1.Startup
5 | {
6 | [StartupTask(BeforeTasks = StartupNodes.Foundation)]
7 | class LibStartup : StartupTask
8 | {
9 | protected override Task RunAsync(StartupContext context)
10 | {
11 | context.Logger.LogInfo("Lib Startup");
12 | return base.RunAsync(context);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/StartupTaskFramework/MainThreadDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using System.Windows;
4 | using dotnetCampus.ApplicationStartupManager;
5 |
6 | namespace WPFDemo.App.StartupTaskFramework
7 | {
8 | // 因为没有在 WPFDemo.Api 引用 WPF 程序集,因此代码写在这里
9 | class MainThreadDispatcher : IMainThreadDispatcher
10 | {
11 | public async Task InvokeAsync(Action action)
12 | {
13 | await Application.Current.Dispatcher.InvokeAsync(action);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-build.yml:
--------------------------------------------------------------------------------
1 | name: .NET Build
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: windows-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 |
13 | - name: Setup .NET
14 | uses: actions/setup-dotnet@v1
15 | with:
16 | dotnet-version: |
17 | 3.1.x
18 | 5.0.x
19 | 6.0.x
20 |
21 | - name: Build with dotnet
22 | run: dotnet build --configuration Release
23 |
24 | - name: Test
25 | run: dotnet test --configuration Release
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Lib1/Startup/Foo2Startup.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 |
3 | using WPFDemo.Api.StartupTaskFramework;
4 |
5 | namespace WPFDemo.Lib1.Startup
6 | {
7 | [StartupTask(BeforeTasks = StartupNodes.CoreUI, AfterTasks = StartupNodes.Foundation)]
8 | public class Foo2Startup : StartupTask
9 | {
10 | protected override Task RunAsync(StartupContext context)
11 | {
12 | context.Logger.LogInfo("Foo2 Startup");
13 | return base.RunAsync(context);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/IStartupContext.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace dotnetCampus.ApplicationStartupManager
4 | {
5 | ///
6 | /// 表示启动任务的上下文接口
7 | ///
8 | public interface IStartupContext
9 | {
10 | ///
11 | /// 等待某个启动任务完成
12 | ///
13 | ///
14 | ///
15 | /// 这是框架层需要支持的能力,因此也就放在此
16 | Task WaitStartupTaskAsync(string startupKey);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/IMainThreadDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace dotnetCampus.ApplicationStartupManager
5 | {
6 | ///
7 | /// 主线程执行调度
8 | ///
9 | /// 大概就是将 WPF 的 Dispatcher 传入
10 | public interface IMainThreadDispatcher
11 | {
12 | ///
13 | /// 调度执行
14 | ///
15 | ///
16 | ///
17 | Task InvokeAsync(Action action);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/Startup/OptionStartup.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 | using WPFDemo.Api.StartupTaskFramework;
3 |
4 | namespace WPFDemo.Api.Startup
5 | {
6 | [StartupTask(BeforeTasks = StartupNodes.Foundation, AfterTasks = "LibStartup")]
7 | class OptionStartup : StartupTask
8 | {
9 | protected override Task RunAsync(StartupContext context)
10 | {
11 | context.Logger.LogInfo("Command " + context.CommandLineOptions.Name);
12 |
13 | return CompletedTask;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Lib1/WPFDemo.Lib1.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Lib1/Startup/Foo3Startup.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 |
3 | using WPFDemo.Api.StartupTaskFramework;
4 |
5 | namespace WPFDemo.Lib1.Startup
6 | {
7 | [StartupTask(BeforeTasks = StartupNodes.CoreUI, AfterTaskList = new[] { nameof(WPFDemo.Lib1.Startup.Foo2Startup), "Foo1Startup" })]
8 | public class Foo3Startup : StartupTask
9 | {
10 | protected override Task RunAsync(StartupContext context)
11 | {
12 | context.Logger.LogInfo("Foo3 Startup");
13 | return base.RunAsync(context);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dotnetCampus.ApplicationStartupManager
2 |
3 | 应用软件的启动流程组件。 支持高性能异步多线程,自动构建启动流程图,动态分配启动任务资源,支持接入预编译框架,支持所有的 .NET 应用。
4 |
5 | Application startup manager for all .NET application.
6 |
7 | | Build | NuGet |
8 | |--|--|
9 | ||[](https://www.nuget.org/packages/dotnetCampus.ApplicationStartupManager)|
10 |
11 | ## 当前进度
12 |
13 | - 基础功能: 可用
14 | - 预编译框架: 待完全开源,参阅 [SourceFusion](https://github.com/dotnet-campus/SourceFusion)
15 | - 使用文档: 待编写
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/Startup/Foo1Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | using dotnetCampus.ApplicationStartupManager;
8 |
9 | using WPFDemo.Api.StartupTaskFramework;
10 |
11 | namespace WPFDemo.Api.Startup
12 | {
13 | [StartupTask(BeforeTasks = StartupNodes.CoreUI, AfterTasks = StartupNodes.Foundation)]
14 | public class Foo1Startup : StartupTask
15 | {
16 | protected override Task RunAsync(StartupContext context)
17 | {
18 | context.Logger.LogInfo("Foo1 Startup");
19 | return base.RunAsync(context);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupTaskHelper.cs:
--------------------------------------------------------------------------------
1 | namespace dotnetCampus.ApplicationStartupManager
2 | {
3 | static class StartupTaskHelper
4 | {
5 | public static string? BuildTasks(params string[]? taskList)
6 | {
7 | if (taskList is null)
8 | {
9 | return null;
10 | }
11 |
12 | if (taskList.Length > 1)
13 | {
14 | return string.Join(";", taskList);
15 | }
16 |
17 | if (taskList.Length == 1)
18 | {
19 | return taskList[0];
20 | }
21 |
22 | return string.Empty;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/StartupTaskFramework/StartupTask.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using dotnetCampus.ApplicationStartupManager;
6 |
7 | namespace WPFDemo.Api.StartupTaskFramework
8 | {
9 | ///
10 | /// 表示一个和当前业务强相关的启动任务
11 | ///
12 | public class StartupTask : StartupTaskBase
13 | {
14 | protected sealed override Task RunAsync(IStartupContext context)
15 | {
16 | return RunAsync((StartupContext) context);
17 | }
18 |
19 | protected virtual Task RunAsync(StartupContext context)
20 | {
21 | return CompletedTask;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 | using System.Windows.Documents;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Imaging;
11 | using System.Windows.Navigation;
12 | using System.Windows.Shapes;
13 |
14 | namespace WPFDemo.App
15 | {
16 | ///
17 | /// Interaction logic for MainWindow.xaml
18 | ///
19 | public partial class MainWindow : Window
20 | {
21 | public MainWindow()
22 | {
23 | InitializeComponent();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/Startup/MainWindowStartup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using dotnetCampus.ApplicationStartupManager;
7 |
8 | using WPFDemo.Api.StartupTaskFramework;
9 |
10 | namespace WPFDemo.App.Startup
11 | {
12 | [StartupTask(BeforeTasks = StartupNodes.AppReady, AfterTasks = StartupNodes.UI, Scheduler = StartupScheduler.UIOnly)]
13 | internal class MainWindowStartup : StartupTask
14 | {
15 | protected override Task RunAsync(StartupContext context)
16 | {
17 | var mainWindow = new MainWindow();
18 | mainWindow.Show();
19 |
20 | return CompletedTask;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/WPFDemo.Api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/WPFDemo.App.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | enable
7 | true
8 | WPFDemo.App.Program
9 | false
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/IStartupManager.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace dotnetCampus.ApplicationStartupManager
4 | {
5 | ///
6 | /// 启动流程管理器
7 | ///
8 | public interface IStartupManager
9 | {
10 | ///
11 | /// 等待某个启动项的完成
12 | ///
13 | ///
14 | ///
15 | Task WaitStartupTaskAsync(string startupTaskKey);
16 |
17 | ///
18 | /// 获取指定类型的启动项
19 | ///
20 | ///
21 | ///
22 | /// 理论上相同的类型的启动项不会被重复加入,基本都是一个启动项一个类型
23 | StartupTaskBase GetStartupTask() where T : StartupTaskBase;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/IStartupTaskWrapper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace dotnetCampus.ApplicationStartupManager
4 | {
5 | ///
6 | /// 启动项在启动框架的信息
7 | ///
8 | public interface IStartupTaskWrapper
9 | {
10 | ///
11 | /// 跟随等待着当前启动项的其他启动项
12 | ///
13 | HashSet FollowTasks { get; }
14 | ///
15 | /// 当前启动项所依赖的其他启动项
16 | ///
17 | HashSet Dependencies { get; }
18 | ///
19 | /// 当前启动项的标识
20 | ///
21 | string StartupTaskKey { get; }
22 | ///
23 | /// 表示当前启动项是否只能在 UI 线程启动
24 | ///
25 | bool UIOnly { get; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | latest
5 | $(MSBuildThisFileDirectory)bin\$(Configuration)
6 | dotnet campus(.NET 职业技术学院)
7 |
8 | MIT
9 | dotnet-campus
10 | https://github.com/dotnet-campus/dotnetCampus.ApplicationStartupManager
11 | https://github.com/dotnet-campus/dotnetCampus.ApplicationStartupManager
12 | Application startup manager
13 |
14 | git
15 | Copyright © 2021 dotnet campus, All Rights Reserved.
16 |
17 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupTaskMetadata.T.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace dotnetCampus.ApplicationStartupManager
6 | {
7 | ///
8 | /// 记录 类型中标记的从 中统一收集元数据。
9 | ///
10 | ///
11 | public class StartupTaskMetadata : StartupTaskMetadata where TStartupTask : StartupTaskBase, new()
12 | {
13 | ///
14 | /// 创建包含 元数据的 的新实例。
15 | ///
16 | public StartupTaskMetadata() : base(typeof(TStartupTask).Name.Replace("Startup", ""), () => new TStartupTask())
17 | {
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupCriticalLevel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace dotnetCampus.ApplicationStartupManager
4 | {
5 | ///
6 | /// 表示启动任务的关键级别,用于在启动流程执行的过程中评估错误的影响范围。
7 | ///
8 | public enum StartupCriticalLevel
9 | {
10 | ///
11 | /// 表示没有评估过此模块的关键级别。
12 | /// 如果你无法评估此模块的关键级别,请保持默认值。
13 | ///
14 | Unset = 0,
15 |
16 | ///
17 | /// 表示这是一个扩展的启动任务,如果此任务初始化失败,软件不会有任何业务功能受到影响。
18 | /// 通常插件、性能和数据监控等模块会使用此级别。
19 | ///
20 | Extension,
21 |
22 | ///
23 | /// 表示这是一个普通的启动任务,如果此任务初始化失败,软件的多数功能不会受到影响。
24 | /// 通常如果一个启动任务初始化的模块不被其他任何模块使用到,那么指定为此级别。
25 | ///
26 | Normal,
27 |
28 | ///
29 | /// 表示这是一个关键启动任务,如果此任务初始化失败,软件将很难正常运行
30 | ///
31 | Critical,
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/StartupTaskFramework/StartupLogger.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Text;
3 |
4 | using dotnetCampus.ApplicationStartupManager;
5 |
6 | namespace WPFDemo.Api.StartupTaskFramework
7 | {
8 | ///
9 | /// 和项目关联的日志
10 | ///
11 | public class StartupLogger : StartupLoggerBase
12 | {
13 | public void LogInfo(string message)
14 | {
15 | Debug.WriteLine(message);
16 | }
17 |
18 | public override void ReportResult(IReadOnlyList wrappers)
19 | {
20 | var stringBuilder = new StringBuilder();
21 | foreach (var keyValuePair in MilestoneDictionary)
22 | {
23 | stringBuilder.AppendLine($"{keyValuePair.Key} - [{keyValuePair.Value.threadName}] Start:{keyValuePair.Value.start} Elapsed:{keyValuePair.Value.elapsed}");
24 | }
25 |
26 | Debug.WriteLine(stringBuilder.ToString());
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/IStartupLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace dotnetCampus.ApplicationStartupManager
6 | {
7 | ///
8 | /// 启动模块的日志
9 | ///
10 | public interface IStartupLogger
11 | {
12 | ///
13 | /// 打点某个里程碑,将会自动与上个里程碑记录时间差
14 | ///
15 | ///
16 | void RecordTime(string milestoneName);
17 | ///
18 | /// 记录某个任务的耗时情况
19 | ///
20 | ///
21 | ///
22 | ///
23 | Task RecordDuration(string taskName, Func> task);
24 | ///
25 | /// 上报启动结果
26 | ///
27 | ///
28 | void ReportResult(IReadOnlyList wrappers);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace dotnetCampus.ApplicationStartupManager
5 | {
6 | ///
7 | /// 启动项的上下文信息,业务方可以自己再定义而不需要使用此类型
8 | ///
9 | internal class StartupContext : IStartupContext
10 | {
11 | public IStartupLogger Logger { get; }
12 |
13 | public Func FastFail { get; }
14 |
15 | private readonly Func _waitStartupTaskAsync;
16 |
17 | Task IStartupContext.WaitStartupTaskAsync(string startupKey) => _waitStartupTaskAsync(startupKey);
18 |
19 | public StartupContext(IStartupLogger logger, /*FileConfigurationRepo configuration,*/
20 | Func fastFailAction, Func waitStartupAsync)
21 | {
22 | Logger = logger;
23 | _waitStartupTaskAsync = waitStartupAsync;
24 | FastFail = fastFailAction ?? (exception => StartupTaskBase.CompletedTask);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/Startup/BusinessStartup.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using dotnetCampus.ApplicationStartupManager;
5 | using WPFDemo.Api.StartupTaskFramework;
6 |
7 | namespace WPFDemo.App.Startup
8 | {
9 | [StartupTask(BeforeTasks = StartupNodes.AppReady, AfterTasks = "MainWindowStartup", Scheduler = StartupScheduler.UIOnly)]
10 | internal class BusinessStartup : StartupTask
11 | {
12 | protected override Task RunAsync(StartupContext context)
13 | {
14 | if (Application.Current.MainWindow.Content is Grid grid)
15 | {
16 | grid.Children.Add(new Button()
17 | {
18 | HorizontalAlignment = HorizontalAlignment.Center,
19 | VerticalAlignment = VerticalAlignment.Bottom,
20 | Margin = new Thickness(10, 10, 10, 10),
21 | Content = "Click"
22 | });
23 | }
24 |
25 | return CompletedTask;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/StartupTaskFramework/StartupManager.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 | using dotnetCampus.Cli;
3 | using dotnetCampus.Configurations.Core;
4 |
5 | namespace WPFDemo.Api.StartupTaskFramework
6 | {
7 | ///
8 | /// 和项目关联的启动管理器,用来注入业务相关的逻辑
9 | ///
10 | public class StartupManager : StartupManagerBase
11 | {
12 | public StartupManager(CommandLine commandLine, FileConfigurationRepo configuration, Func fastFailAction, IMainThreadDispatcher mainThreadDispatcher) : base(new StartupLogger(), fastFailAction, mainThreadDispatcher)
13 | {
14 | var appConfigurator = configuration.CreateAppConfigurator();
15 | Context = new StartupContext(StartupContext, commandLine, (StartupLogger) Logger, configuration, appConfigurator);
16 | }
17 |
18 | private StartupContext Context { get; }
19 |
20 | protected override Task ExecuteStartupTaskAsync(StartupTaskBase startupTask, IStartupContext context, bool uiOnly)
21 | {
22 | return base.ExecuteStartupTaskAsync(startupTask, Context, uiOnly);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/StartupTaskFramework/StartupNodes.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.Api.StartupTaskFramework
2 | {
3 | ///
4 | /// 包含预设的启动节点。
5 | ///
6 | public class StartupNodes
7 | {
8 | ///
9 | /// 基础服务(日志、异常处理、容器、生命周期管理等)请在此节点之前启动,其他业务请在此之后启动。
10 | ///
11 | public const string Foundation = "Foundation";
12 |
13 | ///
14 | /// 需要在任何一个 Window 创建之前启动的任务请在此节点之前。
15 | /// 此节点之后将开始启动 UI。
16 | ///
17 | public const string CoreUI = "CoreUI";
18 |
19 | ///
20 | /// 需要在主 创建之后启动的任务请在此节点之后。
21 | /// 此节点完成则代表主要 UI 已经初始化完毕(但不一定已显示)。
22 | ///
23 | public const string UI = "UI";
24 |
25 | ///
26 | /// 应用程序已完成启动。如果应该显示一个窗口,则此窗口已布局、渲染完毕,对用户完全可见,可开始交互。
27 | /// 不被其他业务依赖的模块可在此节点之后启动。
28 | ///
29 | public const string AppReady = "AppReady";
30 |
31 | ///
32 | /// 任何不关心何时启动的启动任务应该设定为在此节点之前完成。
33 | ///
34 | public const string StartupCompleted = "StartupCompleted";
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.github/workflows/nuget-tag-publish.yml:
--------------------------------------------------------------------------------
1 | name: publish nuget
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: windows-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v1
15 |
16 | - name: Setup .NET
17 | uses: actions/setup-dotnet@v1
18 | with:
19 | dotnet-version: |
20 | 3.1.x
21 | 5.0.x
22 | 6.0.x
23 |
24 | - name: Install dotnet tool
25 | run: dotnet tool install -g dotnetCampus.TagToVersion
26 |
27 | - name: Set tag to version
28 | run: dotnet TagToVersion -t ${{ github.ref }}
29 |
30 | - name: Build with dotnet
31 | run: |
32 | dotnet build --configuration Release
33 | dotnet pack --configuration Release --no-build
34 |
35 | - name: Install Nuget
36 | uses: nuget/setup-nuget@v1
37 | with:
38 | nuget-version: '5.x'
39 |
40 | - name: Add private GitHub registry to NuGet
41 | run: |
42 | nuget sources add -name github -Source https://nuget.pkg.github.com/dotnet-campus/index.json -Username dotnet-campus -Password ${{ secrets.GITHUB_TOKEN }}
43 |
44 | - name: Push generated package to GitHub registry
45 | run: |
46 | nuget push .\bin\Release\*.nupkg -Source github -SkipDuplicate
47 | nuget push .\bin\Release\*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NugetKey }}
48 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/StartupTaskFramework/AssemblyMetadataExporter.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using dotnetCampus.ApplicationStartupManager;
3 | using dotnetCampus.Telescope;
4 |
5 | namespace WPFDemo.Api.StartupTaskFramework
6 | {
7 | public class AssemblyMetadataExporter
8 | {
9 | public AssemblyMetadataExporter(Assembly[] assemblies)
10 | {
11 | _assemblies = assemblies;
12 | }
13 |
14 | public IEnumerable ExportStartupTasks()
15 | {
16 | var collection = Export();
17 | return collection.Select(x => new StartupTaskMetadata(x.RealType.Name.Replace("Startup", ""), x.CreateInstance)
18 | {
19 | Scheduler = x.Attribute.Scheduler,
20 | BeforeTasks = x.Attribute.BeforeTasks,
21 | AfterTasks = x.Attribute.AfterTasks,
22 | //Categories = x.Attribute.Categories,
23 | CriticalLevel = x.Attribute.CriticalLevel,
24 | });
25 | }
26 |
27 | public IEnumerable> Export() where TAttribute : Attribute
28 | {
29 | return AttributedTypes.FromAssembly(_assemblies);
30 | }
31 |
32 | private readonly Assembly[] _assemblies;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.Api/StartupTaskFramework/StartupContext.cs:
--------------------------------------------------------------------------------
1 | using dotnetCampus.ApplicationStartupManager;
2 | using dotnetCampus.Cli;
3 | using dotnetCampus.Configurations;
4 | using dotnetCampus.Configurations.Core;
5 | using WPFDemo.Api.CommandLines;
6 |
7 | namespace WPFDemo.Api.StartupTaskFramework
8 | {
9 | public class StartupContext : IStartupContext
10 | {
11 | public StartupContext(IStartupContext startupContext, CommandLine commandLine, StartupLogger logger, FileConfigurationRepo configuration, IAppConfigurator configs)
12 | {
13 | _startupContext = startupContext;
14 | Logger = logger;
15 | Configuration = configuration;
16 | Configs = configs;
17 | CommandLine = commandLine;
18 | CommandLineOptions = CommandLine.As();
19 | }
20 |
21 | public StartupLogger Logger { get; }
22 |
23 | public CommandLine CommandLine { get; }
24 |
25 | public Options CommandLineOptions { get; }
26 |
27 | public FileConfigurationRepo Configuration { get; }
28 |
29 | public IAppConfigurator Configs { get; }
30 |
31 | public Task ReadCacheAsync(string key, string @default = "")
32 | {
33 | return Configuration.TryReadAsync(key, @default);
34 | }
35 |
36 | private readonly IStartupContext _startupContext;
37 | public Task WaitStartupTaskAsync(string startupKey)
38 | {
39 | return _startupContext.WaitStartupTaskAsync(startupKey);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupTaskBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace dotnetCampus.ApplicationStartupManager
4 | {
5 | ///
6 | /// 启动流程任务创建器
7 | ///
8 | public class StartupTaskBuilder
9 | {
10 | private readonly StartupTaskWrapper _wrapper;
11 | private readonly Action _addDependenciesAction;
12 | private readonly Action _addFollowTasksAction;
13 |
14 | internal StartupTaskBuilder(StartupTaskWrapper wrapper,
15 | Action addDependenciesAction,
16 | Action addFollowTasksAction)
17 | {
18 | _wrapper = wrapper;
19 | _addDependenciesAction = addDependenciesAction;
20 | _addFollowTasksAction = addFollowTasksAction;
21 | }
22 |
23 | ///
24 | /// 设置或获取启动流程的分类
25 | ///
26 | public StartupCategory Categories
27 | {
28 | get => _wrapper.Categories;
29 | set => _wrapper.Categories = value;
30 | }
31 |
32 | ///
33 | /// 加上依赖的启动任务项
34 | ///
35 | ///
36 | ///
37 | public StartupTaskBuilder AddDependencies(string afterTasks)
38 | {
39 | _addDependenciesAction(_wrapper, afterTasks);
40 | return this;
41 | }
42 |
43 | ///
44 | /// 加上跟随当前启动任务项的启动任务项
45 | ///
46 | ///
47 | ///
48 | public StartupTaskBuilder AddFollowTasks(string beforeTasks)
49 | {
50 | _addFollowTasksAction(_wrapper, beforeTasks);
51 | return this;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupTaskAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace dotnetCampus.ApplicationStartupManager
4 | {
5 | ///
6 | /// 启动任务的特性,可以让业务方用来对接,如对接预编译框架,从而收集启动任务项
7 | ///
8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
9 | public sealed class StartupTaskAttribute : Attribute
10 | {
11 | ///
12 | /// 此启动任务的任务必须在指定的其他启动任务之前完成。可以使用 “;” 分隔符指定多个启动任务。
13 | ///
14 | public string? BeforeTasks { get; set; }
15 |
16 | ///
17 | /// 效果上等同于 属性,此属性只是为了开发方便而已
18 | ///
19 | public string[]? BeforeTaskList
20 | {
21 | set
22 | {
23 | BeforeTasks = StartupTaskHelper.BuildTasks(value);
24 | }
25 | get
26 | {
27 | return BeforeTasks?.Split(';');
28 | }
29 | }
30 |
31 | ///
32 | /// 此启动任务的任务将在指定的其他启动任务之后开始执行。可以使用 “;” 分隔符指定多个启动任务。
33 | ///
34 | public string? AfterTasks { get; set; }
35 |
36 | ///
37 | /// 效果上等同于 属性,此属性只是为了开发方便而已
38 | ///
39 | public string[]? AfterTaskList
40 | {
41 | set
42 | {
43 | AfterTasks = StartupTaskHelper.BuildTasks(value);
44 | }
45 | get
46 | {
47 | return AfterTasks?.Split(';');
48 | }
49 | }
50 |
51 | ///
52 | /// 指定启动任务任务的上下文。
53 | ///
54 | public StartupScheduler Scheduler { get; set; } = StartupScheduler.Default;
55 |
56 | ///
57 | /// 获取或设置此启动任务的关键级别。
58 | ///
59 | public StartupCriticalLevel CriticalLevel { get; set; }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-format.yml:
--------------------------------------------------------------------------------
1 | name: Code format check
2 | # 代码格式化机器人,详细请看 [dotnet 基于 dotnet format 的 GitHub Action 自动代码格式化机器人](https://blog.lindexi.com/post/dotnet-%E5%9F%BA%E4%BA%8E-dotnet-format-%E7%9A%84-GitHub-Action-%E8%87%AA%E5%8A%A8%E4%BB%A3%E7%A0%81%E6%A0%BC%E5%BC%8F%E5%8C%96%E6%9C%BA%E5%99%A8%E4%BA%BA.html )
3 |
4 | on:
5 | push:
6 | branches:
7 | - master
8 | jobs:
9 | dotnet-format:
10 | runs-on: windows-latest
11 | steps:
12 |
13 | - name: Checkout repo
14 | uses: actions/checkout@v2
15 | with:
16 | ref: ${{ github.head_ref }}
17 |
18 | - name: Setup .NET
19 | uses: actions/setup-dotnet@v1
20 | with:
21 | dotnet-version: |
22 | 3.1.x
23 | 5.0.x
24 | 6.0.x
25 |
26 | - name: Install dotnetCampus.EncodingNormalior
27 | run: dotnet tool update -g dotnetCampus.EncodingNormalior
28 |
29 | - name: Fix encoding
30 | run: EncodingNormalior -f . --TryFix true
31 |
32 | - name: Install dotnet-format
33 | run: dotnet tool install -g dotnet-format
34 |
35 | - name: Run dotnet format
36 | run: dotnet format
37 |
38 | - name: Commit files
39 | # 下面将使用机器人的账号,你可以替换为你自己的账号
40 | run: |
41 | git config --local user.name "github-actions-dotnet-formatter[bot]"
42 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
43 | git commit -a -m 'Automated dotnet-format update'
44 | continue-on-error: true
45 |
46 | - name: Create Pull Request
47 | # if: steps.format.outputs.has-changes == 'true' # 如果有格式化,才继续
48 | uses: peter-evans/create-pull-request@v3
49 | with:
50 | title: '[Bot] Automated PR to fix formatting errors'
51 | body: |
52 | Automated PR to fix formatting errors
53 | committer: GitHub
54 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
55 | # 以下是给定代码审查者,需要设置仓库有权限的开发者
56 | assignees: lindexi,walterlv,kkwpsv
57 | reviewers: lindexi,walterlv,kkwpsv
58 | # 对应的上传分支
59 | branch: t/bot/fix-codeformatting
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupTaskMetadata.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace dotnetCampus.ApplicationStartupManager
5 | {
6 | ///
7 | /// 记录 类型中标记的从 中统一收集元数据。
8 | ///
9 | public class StartupTaskMetadata
10 | {
11 | private readonly Lazy _taskLazy;
12 |
13 | ///
14 | /// 创建 的新实例。
15 | ///
16 | /// 表示此 的唯一标识符。
17 | /// 此 实例的创建方法。
18 | public StartupTaskMetadata(string key, Func creator)
19 | {
20 | Key = key ?? throw new ArgumentNullException(nameof(key));
21 | _taskLazy = new Lazy(
22 | creator ?? throw new ArgumentNullException(nameof(creator)),
23 | LazyThreadSafetyMode.None);
24 | }
25 |
26 | ///
27 | /// 获取 的唯一标识符。
28 | ///
29 | public string Key { get; }
30 |
31 | ///
32 | /// 获取 Categories 的值
33 | ///
34 | public StartupCategory Categories { get; set; } = StartupCategory.All;
35 |
36 | ///
37 | /// 获取 的值,如果没有标记,则为 null。
38 | ///
39 | public string? BeforeTasks { get; set; }
40 |
41 | ///
42 | /// 获取 的值,如果没有标记,则为 null。
43 | ///
44 | public string? AfterTasks { get; set; }
45 |
46 | ///
47 | /// 获取 的值,如果没有标记,则为 。
48 | ///
49 | public StartupScheduler Scheduler { get; set; }
50 |
51 | ///
52 | /// 根据从元数据中收集到的创建 的方法获取或创建 的实例。
53 | ///
54 | public StartupTaskBase Instance => _taskLazy.Value;
55 |
56 | ///
57 | /// 获取 的值,如果没有获取或设置此启动任务的关键级别,则为 。
58 | ///
59 | public StartupCriticalLevel CriticalLevel { get; set; }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/dotnetCampus.ApplicationStartupManager.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1;netstandard2.0;net45;net5.0;net6.0
5 | true
6 |
7 | true
8 | enable
9 |
10 |
11 |
12 |
13 | true
14 |
15 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
16 |
17 |
18 |
19 |
20 |
21 |
22 | true
23 |
24 |
25 | true
26 |
27 |
28 |
29 |
30 |
31 | true
32 |
33 |
34 |
35 | true
36 | snupkg
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | all
48 | runtime; build; native; contentfiles; analyzers; buildtransitive
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Globalization;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace dotnetCampus.ApplicationStartupManager
11 | {
12 | ///
13 | /// 表示启动任务的基础日志,推荐业务方提供更贴合业务的日志,和日志记录的方法
14 | ///
15 | public class StartupLoggerBase : IStartupLogger
16 | {
17 | private readonly Stopwatch _mainWatch;
18 |
19 | ///
20 | /// 各个启动的里程碑的信息,包括里程碑的所运行的线程名,启动时间和执行时间
21 | ///
22 | /// Key: 启动的里程碑名
23 | ///
24 | ///
25 | /// Value: 启动的信息,包括里程碑的所运行的线程名,启动时间和执行时间。时间都是从 获取
26 | ///
27 | ///
28 | protected ConcurrentDictionary
29 | MilestoneDictionary
30 | { get; } = new ConcurrentDictionary();
31 |
32 | ///
33 | /// 创建启动任务的基础日志,此日志的核心功能就是监控启动项的启动时间
34 | ///
35 | public StartupLoggerBase()
36 | {
37 | _mainWatch = new Stopwatch();
38 | _mainWatch.Start();
39 | }
40 |
41 | ///
42 | public void RecordTime(string milestoneName)
43 | {
44 | var start = MilestoneDictionary.Count > 0
45 | ? MilestoneDictionary.Max(x => x.Value.start + x.Value.elapsed)
46 | : 0;
47 | var end = _mainWatch.ElapsedTicks;
48 | MilestoneDictionary[milestoneName] =
49 | (Thread.CurrentThread.Name ?? Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture),
50 | start, end - start);
51 | }
52 |
53 | ///
54 | public async Task RecordDuration(string taskName, Func> task)
55 | {
56 | var threadName = "null";
57 | var begin = _mainWatch.ElapsedTicks;
58 |
59 | try
60 | {
61 | threadName = await task().ConfigureAwait(false);
62 | }
63 | finally
64 | {
65 | var end = _mainWatch.ElapsedTicks;
66 | var elapse = end - begin;
67 | MilestoneDictionary[taskName] = (threadName, begin, elapse);
68 | }
69 | }
70 |
71 | ///
72 | public virtual void ReportResult(IReadOnlyList wrappers)
73 | {
74 | // 没有实际的可以记录的地方,需要业务方自己实现记录到哪
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/demo/WPFDemo/WPFDemo.App/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Reflection;
4 | using System.Threading.Tasks;
5 |
6 | using dotnetCampus.Cli;
7 | using dotnetCampus.Configurations.Core;
8 |
9 | using WPFDemo.Api.Startup;
10 | using WPFDemo.Api.StartupTaskFramework;
11 | using WPFDemo.App.StartupTaskFramework;
12 | using WPFDemo.Lib1.Startup;
13 |
14 | namespace WPFDemo.App
15 | {
16 | class Program
17 | {
18 | [STAThread]
19 | static void Main(string[] args)
20 | {
21 | var commandLine = CommandLine.Parse(args);
22 |
23 | var app = new App();
24 |
25 | //开始启动任务
26 | StartStartupTasks(commandLine);
27 |
28 | app.Run();
29 | }
30 |
31 | private static void StartStartupTasks(CommandLine commandLine)
32 | {
33 | Task.Run(() =>
34 | {
35 | // 获取应用配置文件逻辑
36 | var configFilePath = "App.coin";
37 | var repo = ConfigurationFactory.FromFile(configFilePath);
38 |
39 | var assemblyMetadataExporter = new AssemblyMetadataExporter(BuildStartupAssemblies());
40 |
41 | var startupManager = new StartupManager(commandLine, repo, HandleShutdownError, new MainThreadDispatcher())
42 | .UseCriticalNodes
43 | (
44 | StartupNodes.Foundation,
45 | StartupNodes.CoreUI,
46 | StartupNodes.UI,
47 | StartupNodes.AppReady,
48 | StartupNodes.StartupCompleted
49 | )
50 | // 导出程序集的启动项
51 | .AddStartupTaskMetadataCollector(() =>
52 | assemblyMetadataExporter.ExportStartupTasks());
53 | startupManager.Run();
54 | });
55 | }
56 |
57 | private static Assembly[] BuildStartupAssemblies()
58 | {
59 | // 初始化预编译收集的所有模块。
60 | return new Assembly[]
61 | {
62 | // WPFDemo.App
63 | typeof(Program).Assembly,
64 | // WPFDemo.Lib1
65 | typeof(Foo2Startup).Assembly,
66 | // WPFDemo.Api
67 | typeof(Foo1Startup).Assembly,
68 | };
69 | }
70 |
71 | private static Task HandleShutdownError(Exception ex)
72 | {
73 | // 这是启动过程的异常,需要进行退出
74 |
75 | #if DEBUG
76 | Debug.WriteLine("========== [初始化过程中出现致命错误,详情请查看异常信息] ==========");
77 | Debug.WriteLine(ex.ToString());
78 |
79 | if (Debugger.IsAttached)
80 | {
81 | Debugger.Break();
82 | }
83 | #endif
84 |
85 | return Task.CompletedTask;
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/dotnetCampus.ApplicationStartupManager.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32014.148
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5D196596-756D-45C2-8A05-C8E4AB8A36E6}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.ApplicationStartupManager", "src\dotnetCampus.ApplicationStartupManager\dotnetCampus.ApplicationStartupManager.csproj", "{F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{A02845A0-C78A-407C-ACF2-529AE6600906}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WPFDemo.App", "demo\WPFDemo\WPFDemo.App\WPFDemo.App.csproj", "{32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFDemo.Api", "demo\WPFDemo\WPFDemo.Api\WPFDemo.Api.csproj", "{68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFDemo.Lib1", "demo\WPFDemo\WPFDemo.Lib1\WPFDemo.Lib1.csproj", "{F4102A0A-E10A-462E-9AB1-0F80C83065D1}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Release|Any CPU = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {F4102A0A-E10A-462E-9AB1-0F80C83065D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {F4102A0A-E10A-462E-9AB1-0F80C83065D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {F4102A0A-E10A-462E-9AB1-0F80C83065D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {F4102A0A-E10A-462E-9AB1-0F80C83065D1}.Release|Any CPU.Build.0 = Release|Any CPU
40 | EndGlobalSection
41 | GlobalSection(SolutionProperties) = preSolution
42 | HideSolutionNode = FALSE
43 | EndGlobalSection
44 | GlobalSection(NestedProjects) = preSolution
45 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272} = {5D196596-756D-45C2-8A05-C8E4AB8A36E6}
46 | {32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF} = {A02845A0-C78A-407C-ACF2-529AE6600906}
47 | {68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32} = {A02845A0-C78A-407C-ACF2-529AE6600906}
48 | {F4102A0A-E10A-462E-9AB1-0F80C83065D1} = {A02845A0-C78A-407C-ACF2-529AE6600906}
49 | EndGlobalSection
50 | GlobalSection(ExtensibilityGlobals) = postSolution
51 | SolutionGuid = {8AAD3A7E-EBB6-4125-9048-4CDE053D079B}
52 | EndGlobalSection
53 | EndGlobal
54 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupTaskWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace dotnetCampus.ApplicationStartupManager
8 | {
9 | [DebuggerDisplay("{StartupTaskKey}:{IsVisited},{VisitedFinishTime}")]
10 | internal class StartupTaskWrapper : IStartupTaskWrapper
11 | {
12 | private readonly StartupManagerBase _manager;
13 | public HashSet FollowTasks { get; private set; } = new HashSet();
14 | public HashSet Dependencies { get; private set; } = new HashSet();
15 |
16 | public string StartupTaskKey { get; }
17 |
18 | internal VisitState IsVisited { get; set; } = VisitState.Unvisited;
19 |
20 | internal int VisitedFinishTime { get; set; } = 0;
21 |
22 | public StartupCategory Categories { get; internal set; } = StartupCategory.All;
23 |
24 | public StartupTaskBase TaskBase { get; internal set; }
25 | // 框架注入,一定不为空
26 | = null!;
27 | public bool UIOnly { get; internal set; }
28 | public StartupCriticalLevel CriticalLevel { get; set; }
29 |
30 | #if DEBUG
31 | ///
32 | /// 启动项是否执行完成,这是一个调试属性
33 | ///
34 | public Task StartupTaskResult => TaskBase.TaskResult;
35 | #endif
36 |
37 | public StartupTaskWrapper(string startupTaskKey, StartupManagerBase manager)
38 | {
39 | _manager = manager;
40 | StartupTaskKey = startupTaskKey;
41 | }
42 |
43 | public async void ExecuteTask(IEnumerable dependencies, StartupContext context)
44 | {
45 | await Task.WhenAll(dependencies.Select(task => task.TaskResult));
46 | #pragma warning disable CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
47 | context.Logger.RecordDuration(StartupTaskKey,
48 | async () =>
49 | {
50 | try
51 | {
52 | if (CriticalLevel == StartupCriticalLevel.Critical)
53 | {
54 | //todo Tracer.Info($"[Startup]关键节点:{StartupTaskKey}开始执行");
55 | }
56 |
57 | var result = await _manager.ExecuteStartupTaskAsync(TaskBase, context, UIOnly);
58 |
59 | if (CriticalLevel == StartupCriticalLevel.Critical)
60 | {
61 | //todo Tracer.Info($"[Startup]关键节点:{StartupTaskKey}执行完成");
62 | }
63 |
64 | return result;
65 | }
66 | catch (Exception ex)
67 | {
68 | if (CriticalLevel == StartupCriticalLevel.Critical)
69 | {
70 | Trace.WriteLine(ex.ToString());
71 | await context.FastFail(ex);
72 | }
73 | else
74 | {
75 | try
76 | {
77 | //todo Tracer.Error(ex);
78 | }
79 | catch
80 | {
81 | // 由于目前正处于启动期间,所以日志模块可能并未真正完成初始化。
82 | // 实际上日志模块一定初始化完毕了,因为日志之前的启动异常都会进入上面的 Critical 分支。
83 | }
84 |
85 | //todo Trace.WriteLine(ex.ToString());
86 | #if DEBUG
87 | // 启动过程中非Critical级别的启动项出现异常,虽然不影响启动,但也应需要修复
88 | Debugger.Break();
89 | #endif
90 | }
91 |
92 | return "";
93 | }
94 | });
95 | #pragma warning restore CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/dotnetCampus.ApplicationStartupManager/StartupTaskBase.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace dotnetCampus.ApplicationStartupManager
6 | {
7 | ///
8 | /// 启动任务项的基类型
9 | ///
10 | public abstract class StartupTaskBase
11 | {
12 | ///
13 | /// 将当前启动任务项加入执行
14 | ///
15 | ///
16 | /// 此函数由启动框架调用
17 | ///
18 | ///
19 | ///
20 | ///
21 | /// 由于我们都在编译期间收集 Attribute 了,当然也能收集使用方到底重写了哪个 Run。
22 | /// 这里传入的 isUIOnly 就是编译期间收集的那个属性。
23 | public async Task JoinAsync(IStartupContext context, bool isUIOnly)
24 | {
25 | // 决定执行 Run 还是 RunAsync。
26 | // 进行性能统计,并报告结果。
27 | if (!isUIOnly)
28 | {
29 | return await Task.Run(async () =>
30 | {
31 | try
32 | {
33 | await RunAsync(context);
34 | }
35 | finally
36 | {
37 | // 即使有异常,也是使用 SetResult 方法,因为启动项不会因为所依赖的启动项抛出异常而不执行
38 | CompletedSource.SetResult(null);
39 | }
40 | return Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
41 | });
42 | }
43 | else
44 | {
45 | try
46 | {
47 | await RunAsync(context);
48 | }
49 | finally
50 | {
51 | // 即使有异常,也是使用 SetResult 方法,因为启动项不会因为所依赖的启动项抛出异常而不执行
52 | CompletedSource.SetResult(null);
53 | }
54 | return Thread.CurrentThread.Name ??
55 | Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
56 | }
57 | }
58 |
59 | ///
60 | /// 启动任务项的实际执行逻辑,由子类继承,实现启动任务项业务逻辑
61 | ///
62 | ///
63 | ///
64 | protected virtual Task RunAsync(IStartupContext context)
65 | {
66 | return CompletedTask;
67 | }
68 |
69 | ///
70 | /// 一个表示执行完成的任务,可以在 作为返回值
71 | ///
72 | protected internal static Task CompletedTask =>
73 | #if NETFRAMEWORK
74 | CompletedCommonTask;
75 | #pragma warning disable IDE0032 // Use auto property
76 | private static readonly Task CompletedCommonTask = Task.FromResult(true);
77 | #pragma warning restore IDE0032 // Use auto property
78 | #else
79 | Task.CompletedTask;
80 | #endif
81 |
82 | ///
83 | /// 获取当前启动任务项可等待任务
84 | ///
85 | public Task TaskResult => CompletedSource.Task;
86 |
87 | private TaskCompletionSource