フォームを閉じるかどうかを決定する FormClosing イベント内で非同期関数を待機できないという問題があります。保存せずに閉じた場合に保存されていない変更を保存するように求める簡単な例を作成しました (メモ帳や Microsoft Word と同様)。私が遭遇した問題は、非同期の Save 関数を待機しているときに、保存関数が完了する前にフォームを閉じてしまい、完了すると終了関数に戻って続行しようとすることです。私の唯一の解決策は、SaveAsync を呼び出す前に終了イベントをキャンセルすることです。保存が成功すると、form.Close() 関数が呼び出されます。この状況を処理するよりクリーンな方法があることを願っています。
このシナリオを再現するには、テキスト ボックス (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.
}
}
}
}
2013/05/23 編集
なぜ私がこれをやろうとしているのかと人々が私に尋ねるのは理解できます。私たちのライブラリのデータ クラスには、多くの場合、非同期で実行するように設計された Save、Load、New、Delete 関数があります (例として SaveAsync を参照してください)。特に FormClosing イベントで非同期に関数を実行することについては、実際にはあまり気にしません。しかし、ユーザーがフォームを閉じる前に保存したい場合は、保存が成功するかどうかを確認する必要があります。保存に失敗した場合は、フォームを閉じるイベントをキャンセルしたいと思います。これを処理する最もクリーンな方法を探しています。