在 FormClosing 事件中等待异步函数 [英] Awaiting Asynchronous function inside FormClosing Event

查看:31
本文介绍了在 FormClosing 事件中等待异步函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一个问题,我无法在 FormClosing 事件中等待异步函数,该函数将确定是否应继续关闭表单.我创建了一个简单的示例,如果您关闭而不保存(很像记事本或 Microsoft Word),它会提示您保存未保存的更改.我遇到的问题是,当我等待异步 Save 函数时,它会在 save 函数完成之前关闭表单,然后在完成后返回关闭函数并尝试继续.我唯一的解决方案是在调用 SaveAsync 之前取消关闭事件,然后如果保存成功它将调用 form.Close() 函数.我希望有一种更简洁的方法来处理这种情况.

要复制场景,请创建一个包含文本框 (txtValue)、复选框 (cbFail) 和按钮 (btnSave) 的表单.这是表单的代码.

使用系统;使用 System.Collections.Generic;使用 System.ComponentModel;使用 System.Data;使用 System.Drawing;使用 System.Linq;使用 System.Text;使用 System.Threading.Tasks;使用 System.Windows.Forms;命名空间 TestZ{公共部分类 Form1 :表单{字符串 cleanValue = "";公共 Form1(){初始化组件();}public bool HasChanges(){返回 (txtValue.Text != cleanValue);}public void ResetChangeState(){清洁值 = txtValue.Text;}private async void btnSave_Click(object sender, EventArgs e){//保存而不立即关心结果等待 SaveAsync();}私有异步任务保存异步(){this.Cursor = Cursors.WaitCursor;btnSave.Enabled = false;txtValue.Enabled = false;cbFail.Enabled = false;任务work = Task.Factory.StartNew(() =>{//在后台线程上做的工作System.Threading.Thread.Sleep(3000);//假装努力工作.如果(cbFail.Checked){MessageBox.Show("保存失败.");返回假;}别的{//将值存入数据库,将当前表单状态标记为clean"MessageBox.Show("保存成功.");重置更改状态();返回真;}});bool retval = 等待工作;btnSave.Enabled = true;txtValue.Enabled = true;cbFail.Enabled = true;this.Cursor = Cursors.Default;返回 retval;}私有异步无效 Form1_FormClosing(对象发送者,FormClosingEventArgs e){如果 (HasChanges()){DialogResult result = MessageBox.Show("有未保存的更改.关闭前要保存吗?", "未保存的更改", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);if (result == System.Windows.Forms.DialogResult.Yes){//这就是我想要处理它的方式 - 但它在等待 Save() 完成时关闭了表单.//bool SaveSuccessful = await Save();//如果(!保存成功)//{//e.Cancel = true;//}//这就是我必须处理它的方式:e.取消=真;bool SaveSuccessful = await SaveAsync();如果(保存成功){this.Close();}}else if (result == System.Windows.Forms.DialogResult.Cancel){e.取消=真;}//如果他们点击否",只需关闭表单.}}}}

<块引用>

编辑 05/23/2013

人们会问我为什么要尝试这样做是可以理解的做这个.我们库中的数据类通常会有保存,设计为异步运行的加载、新建、删除函数(以 SaveAsync 为例).我其实并不那么在意专门在 FormClosing 事件中异步运行该函数.但是如果用户想在关闭表单之前保存,我需要等待看看保存是否成功.如果保存失败,那么我希望它取消表单关闭事件.我只是在寻找最干净的方式处理这个.

解决方案

在我看来,最好的答案是取消关闭表单.总是.取消它,根据需要显示您的对话框,并在用户完成对话框后,以编程方式关闭表单.

这就是我所做的:

async void Window_Closing(object sender, CancelEventArgs args){var w = (Window)sender;var h = (ObjectViewModelHost)w.Content;var v = h.ViewModel;如果 (v != null &&v.IsDirty){args.Cancel = true;w.IsEnabled = false;//调用者返回并且窗口保持打开状态等待 Task.Yield();var c = 等待交互.ConfirmAsync(关闭","您在此窗口中有未保存的更改.如果您退出,它们将被丢弃.",w);如果 (c)w.关闭();//是否关闭无关紧要w.IsEnabled = true;}}

请务必注意对 await Task.Yield() 的调用.如果被调用的异步方法总是异步执行,则没有必要.但是,如果该方法有任何同步路径(即空检查和返回等...),Window_Closing 事件将永远不会完成执行并且对 w.Close() 的调用将引发异常.

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 created a simple example that prompts you to save unsaved changes if you close without saving (much like with notepad or microsoft word). The problem I ran into is that when I await the asynchronous Save function, it proceeds to close the form before the save function has completed, then it comes back to the closing function when it is done and tries to continue. My only solution is to cancel the closing event before calling SaveAsync, then if the save is successful it will call the form.Close() function. I'm hoping there is a cleaner way of handling this situation.

To replicate the scenario, create a form with a text box (txtValue), a checkbox (cbFail), and a button (btnSave). Here is the code for the form.

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

}
}

Edit 05/23/2013

Its understandable that people would ask me why I would be trying to do this. The data classes in our libraries will often have Save, Load, New, Delete functions that are designed to be run asynchronously (See SaveAsync as an example). I do not actually care that much about running the function asynchronously in the FormClosing Event specifically. But if the user wants to save before closing the form, I need it to wait and see if the save succeds or not. If the save fails, then I want it to cancel the form closing event. I'm just looking for the cleanest way to handle this.

解决方案

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.

这篇关于在 FormClosing 事件中等待异步函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆