Awaiting Asynchronous function inside FormClosing Event

后端 未结 6 1123
星月不相逢
星月不相逢 2020-11-27 20:59

I\'m having a problem where I cannot await an asynchronous function inside of the FormClosing event which will determine whether the form close should continue. I have crea

相关标签:
6条回答
  • 2020-11-27 21:19

    You can't keep your form from closing with async/await. And you can get strange results.

    What I would do is creating a Thread and setting its IsBackground property to false (which is false by default) to keep the process alive while form is closing.

    protected override void OnClosing(CancelEventArgs e)
    {
        e.Cancel = false;
        new Thread(() => { 
            Thread.Sleep(5000); //replace this line to save some data.....
            MessageBox.Show("EXITED"); 
        }).Start();
        base.OnClosing(e);
    }
    
    0 讨论(0)
  • 2020-11-27 21:28

    Dialogs handle messages while still keeping the current method on the stack.

    You could show a "Saving..." Dialog in your FormClosing handler, and run the actual saving-operation in a new task, which programmatically closes the dialog once it's done.

    Keep in mind that SaveAsync is running in a non-UI Thread, and needs to marshal any access UI elements via Control.Invoke (see call to decoy.Hide below). Best would probably be to extract any data from controls beforehand, and only use variables in the task.

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
            Form decoy = new Form()
            {
                    ControlBox = false,
                    StartPosition = FormStartPosition.CenterParent,
                    Size = new Size(300, 100),
                    Text = Text, // current window caption
            };
            Label label = new Label()
            {
                    Text = "Saving...",
                    TextAlign = ContentAlignment.MiddleCenter,
                    Dock = DockStyle.Fill,
            };
            decoy.Controls.Add(label);
            var t = Task.Run(async () =>
            {
                    try
                    {
                            // keep form open if saving fails
                            e.Cancel = !await SaveAsync();
                    }
                    finally
                    {
                            decoy.Invoke(new MethodInvoker(decoy.Hide));
                    }
            });
            decoy.ShowDialog(this);
            t.Wait(); //TODO: handle Exceptions
    }
    
    0 讨论(0)
  • 2020-11-27 21:30

    The best answer, in my opinion, is to cancel the Form from closing. Always. Cancel it, display your dialog however you want, and once the user is done with the dialog, programatically close the Form.

    Here's what I do:

    async void Window_Closing(object sender, CancelEventArgs args)
    {
        var w = (Window)sender;
        var h = (ObjectViewModelHost)w.Content;
        var v = h.ViewModel;
    
        if (v != null &&
            v.IsDirty)
        {
            args.Cancel = true;
            w.IsEnabled = false;
    
            // caller returns and window stays open
            await Task.Yield();
    
            var c = await interaction.ConfirmAsync(
                "Close",
                "You have unsaved changes in this window. If you exit they will be discarded.",
                w);
            if (c)
                w.Close();
    
            // doesn't matter if it's closed
            w.IsEnabled = true;
        }
    }
    

    It is important to note the call to await Task.Yield(). It would not be necessary if the async method being called always executed asynchronously. However, if the method has any synchronous paths (ie. null-check and return, etc...) the Window_Closing event will never finish execution and the call to w.Close() will throw an exception.

    0 讨论(0)
  • 2020-11-27 21:33

    I had a similar issue when I tried to handle all of the close event async. I believe it is because there is nothing to block the main thread from moving forward with the actual FormClosingEvents. Just put some inline code after the await and it solves the problem. In my case I save the current state no matter the response (while waiting for the response). You could easily have the task return a current state ready to be saved appropriately once the user responds.

    This worked for me: Spin off task, ask exit confirmation, await task, some inline code.

        Task myNewTask = SaveMyCurrentStateTask();  //This takes a little while so I want it async in the background
    
        DialogResult exitResponse = MessageBox.Show("Are you sure you want to Exit MYAPPNAME? ", "Exit Application?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
    
                await myNewTask;
    
                if (exitResponse == DialogResult.Yes)
                {
                    e.Cancel = false;
                }
                else
                {
                    e.Cancel = true;
                }
    
    0 讨论(0)
  • 2020-11-27 21:34

    I needed to abort closing the form if an exeption was raised during the execution of an async method.

    I'm actually using a Task.Run with .Wait()

    private void Example_FormClosing(object sender, FormClosingEventArgs e)
    {
        try
        {
            Task.Run(async () => await CreateAsync(listDomains)).Wait();
        }
        catch (Exception ex)
        {
            MessageBox.Show($"{ex.Message}", "Attention", MessageBoxButtons.OK, MessageBoxIcon.Error);
            e.Cancel = true;
        }
    }
    
    0 讨论(0)
  • 2020-11-27 21:35

    Why does asynchronous behavior have to be involved? It sounds like something that has to happen in a linear fashion.. I find the simplest solution is usually the right one.

    Alternatively to my code below, you could have the main thread sleep for a second or two, and have the async thread set a flag in the main thread.

    void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (HasChanges())
        {
            DialogResult result = MessageBox.Show("There are unsaved changes. Do you want to save before closing?", "Unsaved Changes", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
            if (result == DialogResult.Yes)
            {
                e.Cancel = true; 
                if(!Save())
                {
                    MessageBox.Show("Your work could not be saved. Check your input/config and try again");
                    e.Cancel = true;
                }
            }
            else if (result == DialogResult.Cancel)
            {
                e.Cancel = true;
            } } }
    
    0 讨论(0)
提交回复
热议问题