0

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).
Matt Smith
  • 17,026
  • 7
  • 53
  • 103
  • 1
    possible duplicate of [Cursor.Current vs. this.Cursor in .Net (C#)](http://stackoverflow.com/questions/302663/cursor-current-vs-this-cursor-in-net-c) – Hans Passant Aug 20 '13 at 18:33
  • @HansPassant, Thanks--that's the answer: the UseWaitCursor is waiting for a WM_SETCURSOR message. This solution works for me. – Matt Smith Aug 20 '13 at 18:39
  • @MattSmith SetCursor will help you show any kind of custom cursors or standard ones, `Wait cursor` is just a very trivial thing. – King King Aug 20 '13 at 18:41

2 Answers2

0

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 (e.g., cursor) before your other UI work, then you can just simply do this:

await Task.Delay(10);
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I don't have synchronous work that I want to do on a background thread. – Matt Smith Aug 20 '13 at 18:29
  • If you really have 2 seconds of UI work to do, then that's how long it takes. There's no magic. – Stephen Cleary Aug 20 '13 at 18:30
  • I don't want any magic--I want to show the wait cursor during those two seconds. – Matt Smith Aug 20 '13 at 18:31
  • 1
    The point is that 2 seconds of UI updates is indicative of a serious design problem. Perhaps you should be using virtualized controls? Wait cursors are not intended to be displayed for UI updates; that said, I've updated my answer with a hack that should do what you want. – Stephen Cleary Aug 20 '13 at 18:33
0

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);
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Why not `await Task.Yield()`? – svick Aug 20 '13 at 21:42
  • @svick I ran a few tests and it wasn't working. That was my first thought as to what should have worked. – Servy Aug 20 '13 at 21:44
  • Hm the empty thread-pool task can easily be inlined non-deterministically. Why does Yield not work? Curious. – usr Aug 20 '13 at 21:50
  • @usr AFAIK `await` never inlines `Task`s. Certainly not to the UI thread. Though there is a small chance that the `Task` will complete before it's `await`ed. – svick Aug 20 '13 at 21:53