Home > OS >  await Task.WhenAll automatically close the program
await Task.WhenAll automatically close the program

Time:11-30

I have a method that doing Chromium browser initialization. After running two asynchronous method, the program automatically close. Did I use the Task.WhenAll wrong?

Here is the entry point of the program:

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));
            }
        }
    }

CodePudding user response:

This is because of how await works. When await acts on a Task that is incomplete, it returns. So your program is working like this:

  1. Main runs, and calls Initializer.Start()
  2. Initializer.Start() runs and calls ChromiumInfo.InitializeAsync()
  3. ChromiumInfo.InitializeAsync() runs until it calls await Task.WhenAll(settingInit, browserSettingInit)
  4. Because Task.WhenAll returns an incomplete Task, ChromiumInfo.InitializeAsync() returns its own incomplete Task and execution returns to Initializer.Start().
  5. The await in Initializer.Start() sees the incomplete Task and returns its own incomplete Task and execution returns to Main().
  6. Because Main() doesn't act on the Task returned by Initializer.Start(), execution continues to the next line, which is the end of the program.

The solution is fairly simple: Change your Main method to async and use await.

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

The ability to make Main() async is a feature introduced in C# 7.1.

  • Related