简体   繁体   中英

Show wait cursor when all work is synchronous

In my Winforms application when you start an operation it may or may not be done asynchronously. In the case that the work is all done synchronously, I cannot get the wait cursor to show up using the same approach I use when the work is done asynchronously.

Here's an example showing the issue:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var t = ButtonClick(button1, DoWorkAsync1);
    }

    private void button2_Click(object sender, EventArgs e)
    {
        var t = ButtonClick(button2, DoWorkAsync2);
    }

    private async Task ButtonClick(Button btn, Func<Task> doWorkAsync)
    {
        // Disable the UI and show the wait cursor
        btn.Text = "Working...";
        btn.Enabled = false;
        UseWaitCursor = true;

        // Application.DoEvents(); // Inserting this causes the problem to go away.
        await doWorkAsync();

        // Simulate an update of the UI taking a long time
        Thread.Sleep(2000); 

        // Renable the UI and stop showing the wait cursor
        UseWaitCursor = false;
        btn.Enabled = true;
    }

    private Task DoWorkAsync1()
    {
        // Work takes some time
        return Task.Delay(2000); 
    }

    private Task DoWorkAsync2()
    {
        // Work doesn't take any time, results are immediately available
        return Task.FromResult<int>(0); 
    }
}

In this example:

  • Clicking button1 does show the Wait cursor (because the work is done asynchronously).
  • Clicking button2 does not show the Wait cursor (because the work is all done synchronously).
  • Clicking either button1 and button2 does result in the UI being disabled as expected.

What is desired is that clicking either button1 or button2 should cause the Wait cursor to be displayed for the entire interval between clicking the button and the UI update work being completed.

Question:

  • Is there a way to solve this without inserting an Application.DoEvent (nor anything equivalent that would cause message pumping to occur), or is it only possible by pumping messages.
  • As an aside: why does the disabling of the control work correctly (unlike the cursor).

If you have synchronous work that you want to do on a background thread, call it in a Task.Run and then await the result (the calling code treats it as asynchronous).

private Task DoWorkAsync1()
{
    return Task.Delay(2000); 
}

private Task DoWorkAsync2()
{
    return Task.Run(() => Thread.Sleep(2000));
}

Alternatively, if your synchronous work really is immediate, and you really want to apply some UI updates (eg, cursor) before your other UI work, then you can just simply do this:

await Task.Delay(10);

Instead of DoEvents throw in:

await Task.Run(() => { });

This will ensure that the awaited task isn't finished right when it's started, so that the remainder of the method will be run as a continuation, rather than synchronously.

Alternatively you could just add a continuation directly to doWorkAsync that you ensure isn't run synchronously:

await doWorkAsync
    .ContinueWith(t => { }, TaskContinuationOptions.HideScheduler);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM