visual unload form evento event control closing close cerrar c# .net-4.5 async-await formclosing

c# - unload - En espera de la función asíncrona dentro de FormClosing Event



form formclosing event c# (5)

Tengo un problema en el que no puedo esperar una función asíncrona dentro del evento FormClosing que determinará si el cierre del formulario debe continuar. He creado un ejemplo simple que le pide que guarde los cambios no guardados si cierra sin guardar (al igual que con el bloc de notas o Microsoft Word). El problema con el que me encontré es que cuando espero la función de Guardar asíncrono, se procede a cerrar el formulario antes de que se complete la función de guardar, luego vuelve a la función de cierre cuando finaliza e intenta continuar. Mi única solución es cancelar el evento de cierre antes de llamar a SaveAsync, luego, si el guardado se realiza correctamente, llamará a la función form.Close (). Espero que haya una forma más limpia de manejar esta situación.

Para replicar el escenario, cree un formulario con un cuadro de texto (txtValue), una casilla de verificación (cbFail) y un botón (btnSave). Aquí está el código para el formulario.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TestZ { public partial class Form1 : Form { string cleanValue = ""; public Form1() { InitializeComponent(); } public bool HasChanges() { return (txtValue.Text != cleanValue); } public void ResetChangeState() { cleanValue = txtValue.Text; } private async void btnSave_Click(object sender, EventArgs e) { //Save without immediate concern of the result await SaveAsync(); } private async Task<bool> SaveAsync() { this.Cursor = Cursors.WaitCursor; btnSave.Enabled = false; txtValue.Enabled = false; cbFail.Enabled = false; Task<bool> work = Task<bool>.Factory.StartNew(() => { //Work to do on a background thread System.Threading.Thread.Sleep(3000); //Pretend to work hard. if (cbFail.Checked) { MessageBox.Show("Save Failed."); return false; } else { //The value is saved into the database, mark current form state as "clean" MessageBox.Show("Save Succeeded."); ResetChangeState(); return true; } }); bool retval = await work; btnSave.Enabled = true; txtValue.Enabled = true; cbFail.Enabled = true; this.Cursor = Cursors.Default; return retval; } private async 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 == System.Windows.Forms.DialogResult.Yes) { //This is how I want to handle it - But it closes the form while it should be waiting for the Save() to complete. //bool SaveSuccessful = await Save(); //if (!SaveSuccessful) //{ // e.Cancel = true; //} //This is how I have to handle it: e.Cancel = true; bool SaveSuccessful = await SaveAsync(); if (SaveSuccessful) { this.Close(); } } else if (result == System.Windows.Forms.DialogResult.Cancel) { e.Cancel = true; } //If they hit "No", just close the form. } } } }

Editar 23/05/2013

Es comprensible que la gente me pregunte por qué estaría tratando de hacer esto. Las clases de datos en nuestras bibliotecas a menudo tendrán funciones de Guardar, Cargar, Nuevo, Eliminar que están diseñadas para ejecutarse de forma asíncrona (vea SaveAsync como ejemplo). En realidad, no me importa mucho ejecutar la función de forma asíncrona en el evento FormClosing Event específicamente. Pero si el usuario desea guardar antes de cerrar el formulario, lo necesito para esperar y ver si la operación de guardar se realiza correctamente o no. Si la operación de guardar falla, quiero cancelar el evento de cierre del formulario. Sólo estoy buscando la forma más limpia de manejar esto.


¿Por qué el comportamiento asíncrono tiene que estar involucrado? Suena como algo que tiene que suceder de una manera lineal. Creo que la solución más simple suele ser la correcta.

Como alternativa a mi código a continuación, puede tener el hilo principal inactivo durante uno o dos segundos, y hacer que el hilo asíncrono establezca una bandera en el hilo principal.

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


La mejor respuesta, en mi opinión, es cancelar el formulario de cierre. Siempre. Cancélelo, visualice el cuadro de diálogo como desee y, una vez que el usuario haya terminado con el cuadro de diálogo, cierre el formulario mediante programación.

Esto es lo que hago:

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


Necesitaba abortar el cierre del formulario si se generaba una excepción durante la ejecución de un método asíncrono.

En realidad estoy usando un Task.Run con .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; } }


No puede evitar que su formulario se cierre con async / await. Y puedes obtener resultados extraños.

Lo que haría sería crear un Thread y establecer su propiedad IsBackground en falso (que es falso por defecto) para mantener el proceso activo mientras se está cerrando el formulario.

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


Tuve un problema similar cuando traté de manejar todo el evento async de cierre. Creo que es porque no hay nada que impida que el subproceso principal avance con los FormClosingEvents reales. Simplemente ponga un poco de código en línea después de la espera y resuelve el problema. En mi caso, guardo el estado actual sin importar la respuesta (mientras espero la respuesta). Fácilmente podría hacer que la tarea devuelva un estado actual listo para ser guardado adecuadamente una vez que el usuario responda.

Esto funcionó para mí: escindir la tarea, pedir confirmación de salida, esperar tarea, algo de código en línea.

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