0

I have a MVP like application, all expensive operations are using Async calls and display an Ajax like gif that indicates the user that something is happening without blocking the main thread.

Example: Data entry form, user clicks Save, an async operation takes place and when it finishes restores the screen to an editable form without blocking the UI thread (in other terms, not blocking other visible windows in the application).

Everything works fine in here, but given the following scenario:

User tries to close the Form, and gets a confirmation message that asks the user if he is sure that he is going to close if he prefers to Save before closing. When the users clicks 'Save' the same logic explained before takes place, but I'm forced to wait for this call to finish in the UI thread (in case there are any errors in the async call or whatever) and I can`t find any way of doing it other way without blocking the UI thread.

Any suggestions? Thanks!

--- Edit ---- What I'm doing right now is waiting on all my WaitHandles in the Presenter with this loop:

while (!WaitHandles.All(h => h.WaitOne(1)))
    Application.DoEvents();

It feels a little dirty.. but at least it simulates non blocking the thread. Is this something that for some reason I should not be doing?

Sebastian Piu
  • 7,838
  • 1
  • 32
  • 50
  • *I'm forced to wait in the UI thread in case there are errors* and *How do I do that without blocking the UI thread?* Those two statements are contradictory. Maybe you can hide the window until the operation completes, then exit or deal with the error? – Tergiver Jan 21 '11 at 16:00
  • Ideally I would like to wait somewhere else than in the UI thread? but is so contradictory as the other statement. See update now – Sebastian Piu Jan 21 '11 at 16:04
  • Let me state my understanding of the question to be sure we're on the same page: My application is about to die, but I need for some async operations to complete before it dies. How do I do that without blocking? The UI thread is dead from the user's point of view (it's terminating), what difference does it make if it blocks or not? – Tergiver Jan 21 '11 at 18:34
  • No, that is not my problem. My problem is when a form that is not the main one is closed, think of it as an MDI application, one of the child forms is closing and I want to do an async operation (Save) before the form is really closed, I might even cancel the form closing if an error exists. – Sebastian Piu Jan 21 '11 at 19:03

1 Answers1

0

Here is an example of the "hide method". Granted, it's not MVP, it's just an example.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

class Form1 : Form
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    public Form1()
    {
        Text = "First Form";
        Button button;
        Controls.Add(button = new Button { Text = "Launch 2nd Form", AutoSize = true, Location = new Point(10, 10) });
        button.Click += (s, e) => new Form2 { StartPosition = FormStartPosition.Manual, Location = new Point(Right, Top) }.Show(this);
    }
}

class Form2 : Form
{
    public Form2()
    {
        Text = "Second Form";
        dirty = true;
    }

    private bool dirty;

    protected override void OnClosing(CancelEventArgs e)
    {
        DialogResult result;
        if (dirty && (result = new ConfirmSaveForm().ShowDialog(this)) != DialogResult.No)
        {
            if (Owner != null)
                Owner.Activate();
            Hide();
            e.Cancel = true;
            SaveAsync(result == DialogResult.Cancel);
        }
        base.OnClosing(e);
    }

    protected override void OnClosed(EventArgs e)
    {
        Trace.WriteLine("Second Form Closed");
        base.OnClosed(e);
    }

    private void SaveAsync(bool fail)
    {
        SaveAsyncBegin();
        var sad = new Action<bool>(PerformAsyncSave);
        sad.BeginInvoke(fail, (ar) =>
        {
            try { sad.EndInvoke(ar); }
            catch (Exception ex) { Invoke(new Action<Exception>(SaveAsyncException), ex); return; }
            Invoke(new Action(SaveAsyncEnd));
        }, null);
    }

    private void SaveAsyncBegin()
    {
        // Update UI for save
    }

    private void PerformAsyncSave(bool fail)
    {
        Trace.WriteLine("Begin Saving");
        Thread.Sleep(1000); // Do some work
        if (fail)
        {
            Trace.WriteLine("Failing Save");
            throw new Exception("Save Failed");
        }
        dirty = false;
    }

    private void SaveAsyncEnd()
    {
        Trace.WriteLine("Save Succeeded");
        Close();
    }

    private void SaveAsyncException(Exception ex)
    {
        Trace.WriteLine("Save Failed");
        Show();
        MessageBox.Show(this, ex.Message, "Save Failed", MessageBoxButtons.OK, MessageBoxIcon.Stop);
    }
}

class ConfirmSaveForm : Form
{
    public ConfirmSaveForm()
    {
        Text = "Confirm Save";
        FormBorderStyle = FormBorderStyle.FixedDialog;
        ControlBox = false;
        ClientSize = new Size(480, 50);
        StartPosition = FormStartPosition.CenterParent;
        Controls.Add(new Button { Text = "Yes, Fail", DialogResult = DialogResult.Cancel, Size = new Size(150, 30), Location = new Point(10, 10) });
        Controls.Add(new Button { Text = "Yes, Succeed", DialogResult = DialogResult.Yes, Size = new Size(150, 30), Location = new Point(160, 10) });
        Controls.Add(new Button { Text = "No", DialogResult = DialogResult.No, Size = new Size(150, 30), Location = new Point(320, 10) });
        AcceptButton = Controls[0] as IButtonControl;
    }
}
Tergiver
  • 14,171
  • 3
  • 41
  • 68