I have been playing with the Visual Studio 11 developer preview and exploring its asynchronous features, specifically the async and await keywords which are new to C# 5.0. These features have actually been available as a CTP (Community Tech Preview) since October 2010, but I had not found time to try it.
I like to keep examples as simple as possible. I have a Windows Forms application which has a long-running task to perform, and I do not want to lock the UI while it runs. Here is my long-running function:
private int slowFunc(int a,int b) { System.Threading.Thread.Sleep(10000); return a + b; }
Yes, it takes 10 seconds! I am going to click a button on a form, call the function, and show the result on a label control.
Now, here is how I might try to achieve the goal of not locking the UI using Visual Studio 2010 and C# 4.0:
private void button1_Click(object sender, EventArgs e) { this.button1.Enabled = false; //prevent re-entry var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2)); this.label1.Text = "Result: " + someTask.Result.ToString(); //oops, blocks calling thread this.button1.Enabled = true; }
Oops, this did not work at all! The reason is that although I have gone to the trouble of creating a Task in order to run the slow function on a background thread, my work is undone when I call the Result property of the Task object – since this blocks the thread until the Task completes.
Here is how you can fix it in Visual Studio 2010 – remember, there is an easier way in C# 5.0 coming up soon:
private void button1_Click(object sender, EventArgs e) { this.button1.Enabled = false; var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); //get UI thread context var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2)); //create and start the Task someTask.ContinueWith(x => { this.label1.Text = "Result: " + someTask.Result.ToString(); this.button1.Enabled = true; }, uiScheduler ); }
This one works. I click the button and the UI does not lock up at all; I can minimize the form, move it around the screen, and so on.
However, I have had to do some extra work. The ContinueWith method tells the Task to run some other code after the background thread has completed. By default this code will not run on the UI thread, which means it will raise an exception when it updates the UI, but you can pass in a TaskScheduler object so that it continues on the UI thread.
Now here is a look at the same problem using C# 5.0. The slowFunc is the same, so I will not retype it. Here is the code for the button click:
private async void button1_Click(object sender, EventArgs e) { this.button1.Enabled = false; var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2)); await someTask; this.label1.Text = "Result: " + someTask.Result.ToString(); this.button1.Enabled = true; }
Less code, same result, which is usually a good thing.
What is going on here though? First, the async modifier is added to the click event handler. This does not mean that the method runs asynchronously. It means that it contains code that will run asynchronously using await. As Eric Lippert explains, it tells the compiler to rewrite the method for you.
Second, there is the await keyword. I cannot improve on Lippert’s explanation so here it is:
The “await” operator … does not mean “this method now blocks the current thread until the asynchronous operation returns”. That would be making the asynchronous operation back into a synchronous operation, which is precisely what we are attempting to avoid. Rather, it means the opposite of that; it means “if the task we are awaiting has not yet completed then sign up the rest of this method as the continuation of that task, and then return to your caller immediately; the task will invoke the continuation when it completes.”
If you refer back to the Visual Studio 2010 examples, you will see that the code is very close to my first, non-working example. In other words,using await makes the code work in the way that intuitively I hoped that it might, without specifically called the ContinueWith method and messing around with the thread context as in the second example.
This is still concurrent programming though. One thing that C# 5.0 cannot prevent is an impatient user clicking several times on the button when the result does not appear immediately, so in all the examples I have disabled the button while the background thread runs.
It seems like syntactic sugar, not really letting you do anything you couldn’t do already, though admittedly making the code a bit cleaner. I wonder though why they couldn’t just as easily add a second parameter to StartNew() that accepts a closure that is in turn executed immediately after the asynchronous method call completes. Unless I am mistaken, wouldn’t that provide the same functionality?
So you would get:
private async void button1_Click(object sender, EventArgs e)
{
this.button1.Enabled = false;
var someTask = Task.Factory.StartNew(() => slowFunc(1, 2),{
this.label1.Text = "Result: " + someTask.Result.ToString();
this.button1.Enabled = true;
});
}
Lynn
It is syntactic sugar. Your suggestion would work too, though you would need to allow for the initial task and the continuation to run on different threads. But I prefer await which lets you write the code in a more natural way.
Tim
Sorry. 🙂 I don’t mean to be a grumpy bear. It’s a nice addition. I think what bugs me is that it’s a new keyword in the language and one which affects the execution of a block and which I would be afraid would be easy to miss visually. Meh… it’ll probably be fine. 🙂
“Unless I am mistaken, wouldn’t that provide the same functionality?”
It would require you to write the continuation manually, which is straightforward in this case, but can be tricky if you’re inside loops, try-catch blocks, other continuations, etc. This is comparable to the “yield” keyword.
I was hoping the async and await keywords would cut out the explicit use of tasks altogether, im very disappointed to say the least, i was hoping you could do this:
private async void button1_Click(object sender, EventArgs e)
{
this.button1.Enabled = false;
int liResult = await slowFunc(1, 2);
this.label1.Text = “Result: ” + liResultResult.ToString();
this.button1.Enabled = true;
}
And have the same outcome, i mean really, surely they could have made it implicitly create the task and start it executing when used with this syntax, whats the point in adding a nice new short keyword for easy access to asynchronous operations if you still have to go and create the task anyway?
Useless, just useless, i would prefer to just simply use the task directly with the TaskScheduler.FromCurrentSynchronizationContext() method.
On another note, i would prefer to implement it in this way, using this pattern you dont have to create a task instance every time you want call the function asynchronously, instead just use the await keyword:
private Task slowFunc(int a,int b)
{
return
Task.Factory.StartNew(() =>
{
System.Threading.Thread.Sleep(10000);
return a + b;
});
}
private async void button1_Click(object sender, EventArgs e)
{
this.button1.Enabled = false;
this.label1.Text = “Result: ” + (await slowFunc(1, 2)).ToString();
this.button1.Enabled = true;
}
Of course one needs to be careful that the values passed to the function are not disposed (or are immutable as in this case).
Can’t wait to see what the VB.NET equivalent looks like. If the way that lambdas are handled by that language are anything to go by, it’ll be syntactically ghastly.
Would this be the equivalent, using the Reactive Framework? (Excuse any syntax errors please):
private void button1_Click(object sender, EventArgs e)
{
this.button1.Enabled = false;
var obs = Observable.Start(slowFunc(1, 2));
obs.ObserveOn(SynchronizationContext.Current).Subscribe((x) =>
{ this.label1.Text = “Result: ” + x.ToString();
this.button1.Enabled = true; });
}
Hi
im working in visual studio 2012 c# , in my program i have method with async keyword..im trying to call normal method from the async method,it working but it doesn’t active another thread ( open the another page or application )
here is my code ,please help me out
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
string text = await RecognizeTextSearchGrammar();
if (text != null)
{
await Speak(string.Format(“Searching Application {0}”, text));
result = text;
match(result); —-> normal method ( trying to open the another application )
}
Thanks in advance
Hi Lavanya,
By reviewing your Post, i has noticed that “Your normal method will exeucte only if there is any response from Speak method which called asynchronously.
By the time you are calling the Speak method, the control suspends this Button1_click and resume once you got the value from Speak Method.
Regards,
Arjun.K