await Task.WhenAll 自动关闭程序

await Task.WhenAll automatically close the program

我有一个方法可以对 Chromium 浏览器进行初始化。 运行 两个异步方法后,程序自动关闭。我是不是用错了Task.WhenAll

这里是程序的入口点:

Start.cs

static class Start
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Initializer.Start();
        }
    }

Initializer.cs

public static class Initializer
    {
        public static async void Start()
        {
            // The task that automatically close the program.
            Task<bool> chromiumInitTask = ChromiumInfo.InitializeAsync();
            await chromiumInitTask;

            Task webviewInitTask = WebViewInfo.InitializeAsync();

            Task guiInitTask = GUIInfo.InitializeAsync();

            HardwareManager.Initialize();

            await webviewInitTask;

            await guiInitTask;

            GUIInfo.Layout.ChangeMainDisplay(ChromiumInfo.Browser);
            Application.Run(GUIInfo.Layout.GetLayoutForm());
        }
    }

ChromiumInfo.cs

public static class ChromiumInfo
    {
        private static CefSettings _settings;
        private static ChromiumWebBrowser _browser;
        private static BrowserSettings _browserSettings;
        private static Dictionary<string, bool> _initTasks = new Dictionary<string, bool>()
        {
            { "Settings", false },
            { "Browser Settings", false },
            { "Browser", false },
            { "Cef", false }
        };
        private static KeyboardHandler _keyboardHandler;

        /// <summary>
        /// An setting which using on initialize.
        /// </summary>
        public static CefSettings Setting => _settings;

        /// <summary>
        /// Representing a browser which can be show in UI.
        /// </summary>
        public static ChromiumWebBrowser Browser => _browser;

        /// <summary>
        /// A keyboard handler which handle various keyboard events.
        /// </summary>
        public static KeyboardHandler KeyboardHandler => _keyboardHandler;

        /// <summary>
        /// Occur when request to change the browser.
        /// </summary>
        public static event RequestChangeBrowserEventHandler RequestChangeBrowser;

        /// <summary>
        /// Initialize all Chromium components in asynchronously.
        /// </summary>
        /// <param name="initAllScripts">Indicate should initialize all scripts contain in the root script directory.</param>
        /// <returns>Return a task can be awaited. True means sucess. Otherwise, return false.</returns>
        public static async Task<bool> InitializeAsync(bool initAllScripts = true)
        {
            Task settingInit = SettingsInitializeAsync();
            Task browserSettingInit = BrowserSettingsInitializeAsync();
            // The below line that automatically close the program.
            await Task.WhenAll(settingInit, browserSettingInit);
            
            Task cefInit = Cef.InitializeAsync(_settings);
            await cefInit;
            _initTasks["Cef"] = true;

            Task browserInit = BrowserInitializeAsync();
            await browserInit;

            Task eventInit = EventInitializeAsync();
            await eventInit;

            Task scriptInit = ScriptInitializeAsync();
            await scriptInit;

            return _initTasks.Values.Where(it => it).Count() == _initTasks.Count;
        }

        private static async Task SettingsInitializeAsync()
        {
            try
            {
                _settings = new CefSettings();

                _settings.CommandLineArgsDisabled = false;
                _settings.CefCommandLineArgs.Clear();
                _settings.CefCommandLineArgs.Add("enable-3d-apis", "1");
                _settings.CefCommandLineArgs.Add("enable-webgl-draft-extensions", "1");
                _settings.CefCommandLineArgs.Add("enable-gpu", "1");
                _settings.CefCommandLineArgs.Add("enable-webgl", "1");
                _settings.CefCommandLineArgs.Add("gpu_compositing", "1");
                _settings.CefCommandLineArgs.Add("ignore-gpu-blocklist", "1");

                await Task.Delay(1000).ConfigureAwait(false);
                _initTasks["Settings"] = true;
            }
            catch (Exception e)
            {
                SystemLog.Write(e);
            }
        }

        private static async Task BrowserSettingsInitializeAsync()
        {
            try
            {
                _browserSettings = new BrowserSettings();
                _browserSettings.WebGl = CefState.Enabled;
                await Task.Delay(1000).ConfigureAwait(false);

                _initTasks["Browser Settings"] = true;
            }
            catch (Exception e)
            {
                SystemLog.Write(e);
            }
        }

        private static async Task BrowserInitializeAsync()
        {
            try
            {
                _browser = new ChromiumWebBrowser(Properties.Settings.Default.DefaultURL);
                _browser.BrowserSettings = _browserSettings;
                _browser.Dock = System.Windows.Forms.DockStyle.Fill;
                await Task.Delay(1000).ConfigureAwait(false);

                _initTasks["Browser"] = true;
            }
            catch (Exception e)
            {
                SystemLog.Write(e);
            }
        }

        private static async Task EventInitializeAsync()
        {
            KeyboardHandler keyboardHandler = new KeyboardHandler();
            WebCommandHandler commandHandler = new WebCommandHandler();
            _browser.ConsoleMessage += keyboardHandler.Handle;
            _browser.ConsoleMessage += commandHandler.Handle;
            _browser.AddressChanged += Custom_AddressChanged;
            _keyboardHandler = keyboardHandler;
            await Task.Delay(1000).ConfigureAwait(false);
        }

        private static async Task ScriptInitializeAsync()
        {
            string scriptPath = $@"{ProgramInfo.RootPath}\scripts";
            if (Directory.Exists(scriptPath))
            {
                var files = Directory.GetFiles(scriptPath, "*.js");
                files?.ToList().ForEach(f => _browser.GetMainFrame().ExecuteJavaScriptAsync(f, _browser.Address));
            }

            await Task.Delay(1000).ConfigureAwait(false);
        }

        private static void Custom_AddressChanged(object sender, AddressChangedEventArgs e)
        {
            var wv2SiteCount = Properties.Settings.Default.WebToWV2.Cast<string>()
                .Where(s => s.IndexOf(e.Address) >= 0).Count();

            if (wv2SiteCount > 0)
            {
                WebViewInfo.Navigate(e.Address);
                RequestChangeBrowser?.Invoke(null, new RequestChangeBrowserEventArgs(WebViewInfo.Browser));
            }
        }
    }

这是因为 await 的工作原理。当await作用于不完整的Task时,它returns。所以你的程序是这样工作的:

  1. Main 运行,并调用 Initializer.Start()
  2. Initializer.Start() 运行并调用 ChromiumInfo.InitializeAsync()
  3. ChromiumInfo.InitializeAsync() 运行直到它调用 await Task.WhenAll(settingInit, browserSettingInit)
  4. 因为Task.WhenAllreturns一个不完整Task,ChromiumInfo.InitializeAsync()returns自身不完整Task而执行returns Initializer.Start().
  5. Initializer.Start()中的await看到不完整Task和returns自己不完整Task并执行returns到Main().
  6. 因为Main()没有作用于Initializer.Start()返回的Task,所以继续执行到下一行,也就是程序的结尾。

解决方案相当简单:将您的 Main 方法更改为 async 并使用 await.

public static async Task Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    await Initializer.Start();
}

使Main()async的能力是a feature introduced in C# 7.1