Ожидание асинхронной функции внутри события FormClosing
У меня проблема, когда я не могу дождаться асинхронной функции внутри события FormClosing, которое определит, должна ли форма закрываться. Я создал простой пример, который предлагает вам сохранить несохраненные изменения, если вы закрываете без сохранения (как в блокноте или microsoft word). Проблема, с которой я столкнулся, заключается в том, что когда я жду асинхронной функции сохранения, она закрывает форму до завершения функции сохранения, а затем возвращается к закрытию функция, когда это сделано и пытается продолжить. Мое единственное решение-отменить событие закрытия перед вызовом SaveAsync, тогда, если сохранение будет успешным, оно вызовет форму.Закрыть (функция). Я надеюсь, что есть более чистый способ справиться с этой ситуацией.
для репликации сценария создайте форму с текстовым полем (txtValue), флажком (cbFail) и кнопкой (btnSave). Вот код формы.
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.
}
}
}
}
изменить 05/23/2013
понятно, что люди спросят меня, почему я буду пытаться сделать это. Классы данных в наших библиотеках часто будут иметь Save, Загрузить, создать, удалить функции, предназначенные для асинхронного запуска (См. SaveAsync в качестве примера). Я на самом деле не так уж нужны запуск функции асинхронно в событии FormClosing специально. Но если пользователь хочет сохранить перед закрытием формы, мне нужно подождать и увидеть, если сохранить succeds или нет. Если сохранить не, тогда я хочу, чтобы это отменить событие закрытия формы. Я просто ищу самый чистый способ с этим справиться.
5 ответов
лучшим ответом, на мой взгляд, является отмена закрытия формы. Всегда. Отмените его, отобразите свой диалог, как хотите, и как только пользователь закончит диалог, программно закройте форму.
вот что я делаю:
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;
}
}
вы не можете удержать свою форму от закрытия с помощью async/await. И вы можете получить странные результаты.
то, что я бы сделал, это создать Thread
и IsBackground
свойство false (по умолчанию false), чтобы сохранить процесс во время закрытия формы.
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);
}
почему должно быть задействовано асинхронное поведение? Это звучит как нечто, что должно происходить линейным образом.. Я считаю, что самое простое решение обычно правильное.
в качестве альтернативы моему коду ниже, вы можете иметь основной поток сна в течение секунды или двух, и асинхронный поток установить флаг в основном потоке.
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;
} } }
У меня была аналогичная проблема, когда я пытался обработать все события close async. Я считаю, что это потому, что нет ничего, чтобы заблокировать основной поток от продвижения вперед с фактическими FormClosingEvents. Просто поместите некоторый встроенный код после ожидания, и это решит проблему. В моем случае я сохраняю текущее состояние независимо от ответа (в ожидании ответа). Вы можете легко вернуть задачу в текущее состояние, готовое к соответствующему сохранению, как только пользователь отвечает.
это сработало для меня: выключите задачу, попросите подтверждение выхода, дождитесь задачи, некоторого встроенного кода.
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;
}
мне нужно было прервать закрытие формы, если exeption был вызван во время выполнения асинхронного метода.
Я на самом деле с помощью Task.Run
С .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;
}
}