1

edit: Application.DoEvents(); this did it. found here: Force GUI update from UI Thread

c#, winforms. i want to increase a number by steps of 1 and have those increments shown inside a listview, so that the user sees the number counting up (for example from 10 to 15).

i have another button that increments just by 1 when clicked and uses the same display(). it works fine and updates the display as expected.

i tried these codes (shortened to save space here):

(1)

for (int i = 0; i < 5; i++) 
{
    var t = Task.Run (async () =>
    {
        myInt++;
        await Task.Delay(300);
        display(); //forces screen refresh
    });
}

(2)

for (int i = 0; i < 5; i++) 
{
    var t = Task.Run (() =>
    {
        myInt++;
        Task.Delay(300).Wait;
        display();
    });
    //t.Wait(); //causes "An unhandled exception of type 'System.AggregateException' occurred in mscorlib.dll"
}

(3)

for (int i = 0; i < 5; i++) 
{
    myInt++;
    display();
    System.Threading.Thread.Sleep(300);
}

(4)

Stopwatch stopwatch = new Stopwatch();
for (int i = 0; i < 5; i++) 
{
    stopwatch.Restart();
    while (true)
    {
        if (stopwatch.ElapsedMilliseconds >= 300)
        {
            break;
        }
    }
stopwatch.Stop();
myInt++;
display();
}

all use this:

private void display()
{   
    myListView.Items.Clear();
    myListView.Items.Add(new ListViewItem(new[] { myInt }));
}

(1) and (2) increment the number by 5 but the display is not updated at all. it shows when the display is updated by some other function.

(3) and (4) increment the number by 5, but the display is only updated after about 1500ms, the display skipping the single steps and displaying just the final result (eg 15).

any suggestions to make this work? can i force a refresh in the display() function somehow?

why does t.Wait(); cause an exception? (i found the task code somewhere on the net)

edit:

(5)

private void team_comm_btn_all_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 5; i++) 
    {
            await Run(i); //error 74 see below
    }
}

private async Task Run(int i)
{
    myInt++;
    display();
    await Task.Delay(300);
}

await Run(i); gives the following: Error 74 The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.

just doing "Run(i)" instead gives a warning that "await" is missing... in this case it compiles and increments by 5 without any delay.

(6)

private void team_comm_btn_all_Click(object sender, EventArgs e)
{
   for (int i = 0; i < 5; i++) 
   {
       var task = Task.Run(async () =>
        {
            await Run(i);
        });
   }
}

private async Task Run(int i)
{
    myInt++;
    display();
    await Task.Delay(300);
}

increments by 5 but does not update display at all.

Community
  • 1
  • 1
letsdance
  • 45
  • 8
  • http://stackoverflow.com/questions/19028374/accessing-ui-controls-in-task-run-with-async-await-on-winforms – Eric J. Oct 04 '15 at 16:09
  • that thread does not cover everything i tried and i found nothing there to solve my problem (see edit above). though it did improve my understanding of Tasks =) – letsdance Oct 04 '15 at 17:08
  • Yeah did not vote to close as duplicate because I wasn't sure if it's the same issue. Have not done WinForms since long before async was released. The fact that "nothing" happens then you suddenly see the final value feels like the windows message queue is not being processed. You would get that behavior if you e.g. have a loop that updates the UI in a normal event handler. Don't know the specifics of how that applies to your situation though. – Eric J. Oct 04 '15 at 17:10
  • Application.DoEvents(); this did it. found here: http://stackoverflow.com/questions/1360944/force-gui-update-from-ui-thread – letsdance Oct 04 '15 at 17:33
  • 1
    That sometimes "works" but is not an elegant solution. Your code periodically triggers the event queue to be triggered. If your code doesn't do this very often, the UI will be sluggish. Since this *does* solve the issue, it probably means that you are blocking the UI thread and not doing your processing on a separate thread. – Eric J. Oct 04 '15 at 17:38
  • as long as it works it's better than everything else that did not work. i have no idea how to separate UI and processing threads. maybe i'll look into that, but it doesn't seem important (there won't be any significant amount of processing). edit: but a database access... maybe it can be important. – letsdance Oct 05 '15 at 00:00

1 Answers1

0

Normally you would use System.Windows.Forms.Timer for that. But async/await makes such things trivial as soon as you understand it (and read carefully the compiler warning and error messages). Shortly, (5) is the way to go with a small modification resolving the compiler error.
But let start from the beginning. Assuming you first write a normal synchronous version like this (basically your (3))

private void team_comm_btn_all_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 5; i++) 
    {
        if (i != 0) System.Threading.Thread.Sleep(300);
        myInt++;
        display();
    }
}

just to see that the UI is not updating. So you decide to turn it into asynchronous, which with the help of async/await is simple replacing Thread.Sleep with Task.Delay like this

private void team_comm_btn_all_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 5; i++) 
    {
        if (i != 0) await Task.Delay(300);
        myInt++;
        display();
    }
}

But now it fails to compile with the following error

Error 74 The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.

The message is clear enough. You must mark your method with the async keyword. What about the other recommendation of changing the return type to Task, normally you should take it into consideration, but since you are inside a event handler, i.e. a method which signature cannot be changed, you can ignore that part - that's the exact case why async void is allowed.

To conclude, your final code will look like this

private async void team_comm_btn_all_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 5; i++) 
    {
        if (i != 0) await Task.Delay(300);
        myInt++;
        display();
    }
}
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • thx, will test this out sometimes. Application.DoEvents() worked fine in this case but it seems i'm running into similar troubles at other points of the code where a more elegant solution would be better. – letsdance Nov 06 '15 at 22:04