1

I am trying to learn async await. I am trying a simple C# Windows Forms application with just 1 textbox and 1 button.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

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

        const bool USE_GET_STRING_DIRECTLY = true; // just for switching logic on button1_click

        private async void button1_Click(object sender, EventArgs e)
        {

            textBox1.Clear();
            if (USE_GET_STRING_DIRECTLY)
            {
                // Code #1 <<<---------------------
                textBox1.Text += await GetString();
            }
            else
            {
                // Code #2 <<<---------------------
                var s = await GetString();
                textBox1.Text += s;
            }
        }

        async Task<string> GetString()
        {
            await Task.Delay(2000);
            return "Test";
        }

    }
}

I am using USE_GET_STRING_DIRECTLY just as a sort of conditional compilation to switch between Code #1 and Code #2.

If USE_GET_STRING_DIRECTLY is true, Code #1 will be executed

textBox1.Text += await GetString();

I press the button twice within 500msec. I will see the text "Test" on the textbox.

Now, I set the USE_GET_STRING_DIRECTLY to false, Code #2 will be executed:

var s = await GetString();
textBox1.Text += s;

I press the button twice within 500msec. I will see the text "TestTest" on the textbox. It is printing the Test twice.

It seems to me that code #1 and #2 are the same but they are exhibiting different behavior. The difference is that other one is equated to a variable, while the other one is equated to a Textbox.Text. What could be the reason for this? I am pretty new to async/await so I am pretty sure I am missing something here.

  • 1
    Looks to me like they should behave the same way – Joe Phillips Apr 03 '18 at 15:08
  • 3
    The `await` in the first case happens **after** the `Textbox1.Text` is captured, resulting in an overwrite of whatever happens between the capture and the await happening. In the second example the textbox text is not captured and will be appended. The two sets of code are not equivalent. – Ron Beyer Apr 03 '18 at 15:13
  • @RonBeyer I don't follow. The only difference is a variable allocation that I can see – Joe Phillips Apr 03 '18 at 15:15
  • like @JeroenMostert said you should clear before you set the text to the textbox to see the exact result or else it will append the previous one. – Surenthar Pitchai Apr 03 '18 at 15:16
  • @JeroenMostert This code only ever actually uses a single thread. The malformed ordering is a result of asynchrony, not multithreading. You are indeed correct about the order, just not the reason. – Servy Apr 03 '18 at 15:16
  • @SurentharP Far easier to just set the text rather than appending, if you actually want to overwrite the existing data. – Servy Apr 03 '18 at 15:16
  • 1
    @JoePhillips `textBox1.Text += await...` is the key part there, expand it out to `textBox1.Text = textBox1.Text + await ...` the value of `textBox1.Text` is captured on the original context and assigned when the await returns. – Ron Beyer Apr 03 '18 at 15:17
  • FYI, here is an example of async/await that I wrote up a while back: https://stackoverflow.com/questions/14455293/how-and-when-to-use-async-and-await/44204614#44204614 – Joe Phillips Apr 03 '18 at 15:18
  • @RonBeyer Not sure what you mean. Everything is happening on the same context here because it's being restored for each await – Joe Phillips Apr 03 '18 at 15:21
  • 1
    FWIW, this is not a good example considering you said you are "starting to learn async await". Recommend going to a blog/book/some expert resource to get a better example. – P.Brian.Mackey Apr 03 '18 at 15:21
  • 1
    @Servy - I don't like how the example mixes event type asynchronous programming with await. It's quite unclear. Especially if you compare it to the breakdown in Jon Skeet's 4th edition examples. Not saying newbies should go get that book, just for this particular case his explanation and example make way more sense. – P.Brian.Mackey Apr 03 '18 at 15:26
  • @P.Brian.Mackey How else would you use `await` in a winforms app? – Servy Apr 03 '18 at 15:29
  • await doesn't have to be used in a winforms app at all...it could be used anywhere else – Joe Phillips Apr 03 '18 at 15:29
  • @RonBeyer I see what you're saying now. Trying to come up with an explanation for it – Joe Phillips Apr 03 '18 at 15:31
  • @JoePhillips What would you suggest instead as a good way of learning how to use it then? – Servy Apr 03 '18 at 15:36
  • @Servy I already linked an example but I typically use LINQPad to go through scenarios like this – Joe Phillips Apr 03 '18 at 15:38
  • 1
    @JoePhillips Console apps tend to be fairly poor contexts for exploring `await` as there's no message loop or synchronization context, and they're inherently *synchronous* unless you write a bunch of reasonably complex code (that's bad for an introduction to the topic) in order to create an asynchronous entry point, and that complex work you'd be creating is pretty much exactly what `Application.Run` does for a winforms app. – Servy Apr 03 '18 at 15:41
  • @Servy I suppose... but that obviously won't work for the other operating systems that .net runs on – Joe Phillips Apr 03 '18 at 16:06

1 Answers1

5

So the key points here is the order that the operands of the += operator are evaluated, and where in your code you have the await.

The way that the += operator works is that it figures out what the first (left) operand is, in this case, the variable textBox1.Text, then it gets the value from that variable, and then it figures out what the second (right) operand is, which is either await GetString or s, depending on which snippet you're using, then it combines the two values, then it sets the new value to the variable.

This means that in the first snippet you resolve textBox1.Text to a value before awaiting GetString, whereas in the second example you resolve TextBox1.Text to its value after awaiting GetString.

Having said all of that, I wouldn't suggest using the second snippet over the first. The proper way to avoid this problem is to avoid calling Clear and then appending text some time later. If you want to overwrite the existing text, rather than appending to it, then use = rather than += to set the text, as that will set it, not overwrite it. Additionally, if you have an asynchronous handler for a UI event it's often appropriate to disable that UI control at the start of the handler, and then enable it at the end, if you don't want the user to perform that action (in this case, pressing the button) while an earlier handler is still running. Sometimes you want to allow it, but often (as appears to be the case here) you don't.

Servy
  • 202,030
  • 26
  • 332
  • 449