Merge pull request #6 from dotnet-campus/t/lindexi/App

加上测试代码
This commit is contained in:
LanXiaofang
2022-03-17 09:17:55 +08:00
committed by GitHub
30 changed files with 628 additions and 4 deletions

View File

@@ -0,0 +1,13 @@
using dotnetCampus.Cli;
namespace WPFDemo.Api.CommandLines
{
/// <summary>
/// 启动参数
/// </summary>
public class Options
{
[Option("Name")]
public string? Name { set; get; }
}
}

View File

@@ -0,0 +1,6 @@
using dotnetCampus.ApplicationStartupManager;
using dotnetCampus.Telescope;
using WPFDemo.Api.StartupTaskFramework;
[assembly: MarkExport(typeof(StartupTask), typeof(StartupTaskAttribute))]

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using dotnetCampus.ApplicationStartupManager;
using WPFDemo.Api.StartupTaskFramework;
namespace WPFDemo.Api.Startup
{
[StartupTask(BeforeTasks = StartupNodes.CoreUI, AfterTasks = StartupNodes.Foundation)]
public class Foo1Startup : StartupTask
{
protected override Task RunAsync(StartupContext context)
{
context.Logger.LogInfo("Foo1 Startup");
return base.RunAsync(context);
}
}
}

View File

@@ -0,0 +1,16 @@
using dotnetCampus.ApplicationStartupManager;
using WPFDemo.Api.StartupTaskFramework;
namespace WPFDemo.Api.Startup
{
[StartupTask(BeforeTasks = StartupNodes.Foundation, AfterTasks = "LibStartup")]
class OptionStartup : StartupTask
{
protected override Task RunAsync(StartupContext context)
{
context.Logger.LogInfo("Command " + context.CommandLineOptions.Name);
return CompletedTask;
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Reflection;
using dotnetCampus.ApplicationStartupManager;
using dotnetCampus.Telescope;
namespace WPFDemo.Api.StartupTaskFramework
{
public class AssemblyMetadataExporter
{
public AssemblyMetadataExporter(Assembly[] assemblies)
{
_assemblies = assemblies;
}
public IEnumerable<StartupTaskMetadata> ExportStartupTasks()
{
var collection = Export<StartupTask, StartupTaskAttribute>();
return collection.Select(x => new StartupTaskMetadata(x.RealType.Name.Replace("Startup", ""), x.CreateInstance)
{
Scheduler = x.Attribute.Scheduler,
BeforeTasks = x.Attribute.BeforeTasks,
AfterTasks = x.Attribute.AfterTasks,
//Categories = x.Attribute.Categories,
CriticalLevel = x.Attribute.CriticalLevel,
});
}
public IEnumerable<AttributedTypeMetadata<TBaseClassOrInterface, TAttribute>> Export<TBaseClassOrInterface, TAttribute>() where TAttribute : Attribute
{
return AttributedTypes.FromAssembly<TBaseClassOrInterface, TAttribute>(_assemblies);
}
private readonly Assembly[] _assemblies;
}
}

View File

@@ -0,0 +1,42 @@
using dotnetCampus.ApplicationStartupManager;
using dotnetCampus.Cli;
using dotnetCampus.Configurations;
using dotnetCampus.Configurations.Core;
using WPFDemo.Api.CommandLines;
namespace WPFDemo.Api.StartupTaskFramework
{
public class StartupContext : IStartupContext
{
public StartupContext(IStartupContext startupContext, CommandLine commandLine, StartupLogger logger, FileConfigurationRepo configuration, IAppConfigurator configs)
{
_startupContext = startupContext;
Logger = logger;
Configuration = configuration;
Configs = configs;
CommandLine = commandLine;
CommandLineOptions = CommandLine.As<Options>();
}
public StartupLogger Logger { get; }
public CommandLine CommandLine { get; }
public Options CommandLineOptions { get; }
public FileConfigurationRepo Configuration { get; }
public IAppConfigurator Configs { get; }
public Task<string> ReadCacheAsync(string key, string @default = "")
{
return Configuration.TryReadAsync(key, @default);
}
private readonly IStartupContext _startupContext;
public Task WaitStartupTaskAsync(string startupKey)
{
return _startupContext.WaitStartupTaskAsync(startupKey);
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Diagnostics;
using System.Text;
using dotnetCampus.ApplicationStartupManager;
namespace WPFDemo.Api.StartupTaskFramework
{
/// <summary>
/// 和项目关联的日志
/// </summary>
public class StartupLogger : StartupLoggerBase
{
public void LogInfo(string message)
{
Debug.WriteLine(message);
}
public override void ReportResult(IReadOnlyList<IStartupTaskWrapper> wrappers)
{
var stringBuilder = new StringBuilder();
foreach (var keyValuePair in MilestoneDictionary)
{
stringBuilder.AppendLine($"{keyValuePair.Key} - [{keyValuePair.Value.threadName}] Start:{keyValuePair.Value.start} Elapsed:{keyValuePair.Value.elapsed}");
}
Debug.WriteLine(stringBuilder.ToString());
}
}
}

View File

@@ -0,0 +1,25 @@
using dotnetCampus.ApplicationStartupManager;
using dotnetCampus.Cli;
using dotnetCampus.Configurations.Core;
namespace WPFDemo.Api.StartupTaskFramework
{
/// <summary>
/// 和项目关联的启动管理器,用来注入业务相关的逻辑
/// </summary>
public class StartupManager : StartupManagerBase
{
public StartupManager(CommandLine commandLine, FileConfigurationRepo configuration, Func<Exception, Task> fastFailAction, IMainThreadDispatcher mainThreadDispatcher) : base(new StartupLogger(), fastFailAction, mainThreadDispatcher)
{
var appConfigurator = configuration.CreateAppConfigurator();
Context = new StartupContext(StartupContext, commandLine, (StartupLogger) Logger, configuration, appConfigurator);
}
private StartupContext Context { get; }
protected override Task<string> ExecuteStartupTaskAsync(StartupTaskBase startupTask, IStartupContext context, bool uiOnly)
{
return base.ExecuteStartupTaskAsync(startupTask, Context, uiOnly);
}
}
}

View File

@@ -0,0 +1,36 @@
namespace WPFDemo.Api.StartupTaskFramework
{
/// <summary>
/// 包含预设的启动节点。
/// </summary>
public class StartupNodes
{
/// <summary>
/// 基础服务(日志、异常处理、容器、生命周期管理等)请在此节点之前启动,其他业务请在此之后启动。
/// </summary>
public const string Foundation = "Foundation";
/// <summary>
/// 需要在任何一个 Window 创建之前启动的任务请在此节点之前。
/// 此节点之后将开始启动 UI。
/// </summary>
public const string CoreUI = "CoreUI";
/// <summary>
/// 需要在主 <see cref="Window"/> 创建之后启动的任务请在此节点之后。
/// 此节点完成则代表主要 UI 已经初始化完毕(但不一定已显示)。
/// </summary>
public const string UI = "UI";
/// <summary>
/// 应用程序已完成启动。如果应该显示一个窗口,则此窗口已布局、渲染完毕,对用户完全可见,可开始交互。
/// 不被其他业务依赖的模块可在此节点之后启动。
/// </summary>
public const string AppReady = "AppReady";
/// <summary>
/// 任何不关心何时启动的启动任务应该设定为在此节点之前完成。
/// </summary>
public const string StartupCompleted = "StartupCompleted";
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using dotnetCampus.ApplicationStartupManager;
namespace WPFDemo.Api.StartupTaskFramework
{
/// <summary>
/// 表示一个和当前业务强相关的启动任务
/// </summary>
public class StartupTask : StartupTaskBase
{
protected sealed override Task RunAsync(IStartupContext context)
{
return RunAsync((StartupContext) context);
}
protected virtual Task RunAsync(StartupContext context)
{
return CompletedTask;
}
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\dotnetCampus.ApplicationStartupManager\dotnetCampus.ApplicationStartupManager.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="dotnetCampus.CommandLine" Version="3.3.1-alpha03"/>
<PackageReference Include="dotnetCampus.Configurations" Version="1.6.9" />
<PackageReference Include="dotnetCampus.TelescopeSource" Version="1.0.0-alpha02" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
<Application x:Class="WPFDemo.App.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFDemo.App">
<Application.Resources>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace WPFDemo.App
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@@ -0,0 +1,12 @@
<Window x:Class="WPFDemo.App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFDemo.App"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WPFDemo.App
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
using dotnetCampus.Cli;
using dotnetCampus.Configurations.Core;
using WPFDemo.Api.Startup;
using WPFDemo.Api.StartupTaskFramework;
using WPFDemo.App.StartupTaskFramework;
using WPFDemo.Lib1.Startup;
namespace WPFDemo.App
{
class Program
{
[STAThread]
static void Main(string[] args)
{
var commandLine = CommandLine.Parse(args);
var app = new App();
//开始启动任务
StartStartupTasks(commandLine);
app.Run();
}
private static void StartStartupTasks(CommandLine commandLine)
{
Task.Run(() =>
{
// 获取应用配置文件逻辑
var configFilePath = "App.coin";
var repo = ConfigurationFactory.FromFile(configFilePath);
var assemblyMetadataExporter = new AssemblyMetadataExporter(BuildStartupAssemblies());
var startupManager = new StartupManager(commandLine, repo, HandleShutdownError, new MainThreadDispatcher())
.UseCriticalNodes
(
StartupNodes.Foundation,
StartupNodes.CoreUI,
StartupNodes.UI,
StartupNodes.AppReady,
StartupNodes.StartupCompleted
)
// 导出程序集的启动项
.AddStartupTaskMetadataCollector(() =>
assemblyMetadataExporter.ExportStartupTasks());
startupManager.Run();
});
}
private static Assembly[] BuildStartupAssemblies()
{
// 初始化预编译收集的所有模块。
return new Assembly[]
{
// WPFDemo.App
typeof(Program).Assembly,
// WPFDemo.Lib1
typeof(Foo2Startup).Assembly,
// WPFDemo.Api
typeof(Foo1Startup).Assembly,
};
}
private static Task HandleShutdownError(Exception ex)
{
// 这是启动过程的异常,需要进行退出
#if DEBUG
Debug.WriteLine("========== [初始化过程中出现致命错误,详情请查看异常信息] ==========");
Debug.WriteLine(ex.ToString());
if (Debugger.IsAttached)
{
Debugger.Break();
}
#endif
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,6 @@
using dotnetCampus.ApplicationStartupManager;
using dotnetCampus.Telescope;
using WPFDemo.Api.StartupTaskFramework;
[assembly: MarkExport(typeof(StartupTask), typeof(StartupTaskAttribute))]

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"WPFDemo.App": {
"commandName": "Project",
"commandLineArgs": "-Name ApplicationStartupManager"
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using dotnetCampus.ApplicationStartupManager;
using WPFDemo.Api.StartupTaskFramework;
namespace WPFDemo.App.Startup
{
[StartupTask(BeforeTasks = StartupNodes.AppReady, AfterTasks = "MainWindowStartup", Scheduler = StartupScheduler.UIOnly)]
internal class BusinessStartup : StartupTask
{
protected override Task RunAsync(StartupContext context)
{
if (Application.Current.MainWindow.Content is Grid grid)
{
grid.Children.Add(new Button()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
Margin = new Thickness(10, 10, 10, 10),
Content = "Click"
});
}
return CompletedTask;
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using dotnetCampus.ApplicationStartupManager;
using WPFDemo.Api.StartupTaskFramework;
namespace WPFDemo.App.Startup
{
[StartupTask(BeforeTasks = StartupNodes.AppReady, AfterTasks = StartupNodes.UI, Scheduler = StartupScheduler.UIOnly)]
internal class MainWindowStartup : StartupTask
{
protected override Task RunAsync(StartupContext context)
{
var mainWindow = new MainWindow();
mainWindow.Show();
return CompletedTask;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using dotnetCampus.ApplicationStartupManager;
namespace WPFDemo.App.StartupTaskFramework
{
// 因为没有在 WPFDemo.Api 引用 WPF 程序集,因此代码写在这里
class MainThreadDispatcher : IMainThreadDispatcher
{
public async Task InvokeAsync(Action action)
{
await Application.Current.Dispatcher.InvokeAsync(action);
}
}
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<StartupObject>WPFDemo.App.Program</StartupObject>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\WPFDemo.Api\WPFDemo.Api.csproj" />
<ProjectReference Include="..\WPFDemo.Lib1\WPFDemo.Lib1.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="dotnetCampus.CommandLine" Version="3.3.1-alpha03"/>
<PackageReference Include="dotnetCampus.TelescopeSource" Version="1.0.0-alpha02" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
using dotnetCampus.ApplicationStartupManager;
using dotnetCampus.Telescope;
using WPFDemo.Api.StartupTaskFramework;
[assembly: MarkExport(typeof(StartupTask), typeof(StartupTaskAttribute))]

View File

@@ -0,0 +1,16 @@
using dotnetCampus.ApplicationStartupManager;
using WPFDemo.Api.StartupTaskFramework;
namespace WPFDemo.Lib1.Startup
{
[StartupTask(BeforeTasks = StartupNodes.CoreUI, AfterTasks = StartupNodes.Foundation)]
public class Foo2Startup : StartupTask
{
protected override Task RunAsync(StartupContext context)
{
context.Logger.LogInfo("Foo2 Startup");
return base.RunAsync(context);
}
}
}

View File

@@ -0,0 +1,16 @@
using dotnetCampus.ApplicationStartupManager;
using WPFDemo.Api.StartupTaskFramework;
namespace WPFDemo.Lib1.Startup
{
[StartupTask(BeforeTasks = StartupNodes.CoreUI, AfterTaskList = new[] { nameof(WPFDemo.Lib1.Startup.Foo2Startup), "Foo1Startup" })]
public class Foo3Startup : StartupTask
{
protected override Task RunAsync(StartupContext context)
{
context.Logger.LogInfo("Foo3 Startup");
return base.RunAsync(context);
}
}
}

View File

@@ -0,0 +1,15 @@
using dotnetCampus.ApplicationStartupManager;
using WPFDemo.Api.StartupTaskFramework;
namespace WPFDemo.Lib1.Startup
{
[StartupTask(BeforeTasks = StartupNodes.Foundation)]
class LibStartup : StartupTask
{
protected override Task RunAsync(StartupContext context)
{
context.Logger.LogInfo("Lib Startup");
return base.RunAsync(context);
}
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\WPFDemo.Api\WPFDemo.Api.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="dotnetCampus.TelescopeSource" Version="1.0.0-alpha02" />
</ItemGroup>
</Project>

View File

@@ -1,12 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31605.320
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5D196596-756D-45C2-8A05-C8E4AB8A36E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.ApplicationStartupManager", "src\dotnetCampus.ApplicationStartupManager\dotnetCampus.ApplicationStartupManager.csproj", "{F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{A02845A0-C78A-407C-ACF2-529AE6600906}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WPFDemo.App", "demo\WPFDemo\WPFDemo.App\WPFDemo.App.csproj", "{32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFDemo.Api", "demo\WPFDemo\WPFDemo.Api\WPFDemo.Api.csproj", "{68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFDemo.Lib1", "demo\WPFDemo\WPFDemo.Lib1\WPFDemo.Lib1.csproj", "{F4102A0A-E10A-462E-9AB1-0F80C83065D1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -17,12 +25,27 @@ Global
{F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Release|Any CPU.Build.0 = Release|Any CPU
{32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF}.Release|Any CPU.Build.0 = Release|Any CPU
{68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32}.Release|Any CPU.Build.0 = Release|Any CPU
{F4102A0A-E10A-462E-9AB1-0F80C83065D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4102A0A-E10A-462E-9AB1-0F80C83065D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4102A0A-E10A-462E-9AB1-0F80C83065D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4102A0A-E10A-462E-9AB1-0F80C83065D1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F7ED61F4-920C-49EB-8DC1-74B2BE6AF272} = {5D196596-756D-45C2-8A05-C8E4AB8A36E6}
{32CDDF87-194D-4A6C-9DF5-B9D21A8F45AF} = {A02845A0-C78A-407C-ACF2-529AE6600906}
{68AD8EB7-A9A1-4EA7-A419-9BA1F74ACA32} = {A02845A0-C78A-407C-ACF2-529AE6600906}
{F4102A0A-E10A-462E-9AB1-0F80C83065D1} = {A02845A0-C78A-407C-ACF2-529AE6600906}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8AAD3A7E-EBB6-4125-9048-4CDE053D079B}

View File

@@ -444,7 +444,7 @@ namespace dotnetCampus.ApplicationStartupManager
private static string StartupTypeToKey(Type type)
=> type.Name.Remove(type.Name.Length - "startup".Length);
internal virtual Task<string> ExecuteStartupTaskAsync(StartupTaskBase startupTask, IStartupContext context, bool uiOnly)
protected internal virtual Task<string> ExecuteStartupTaskAsync(StartupTaskBase startupTask, IStartupContext context, bool uiOnly)
{
return startupTask.JoinAsync(context, uiOnly);
}