I have a question about async\await in a C# .NET app. I'm actually trying to solve this problem in a Kinect based application but to help me illustrate, I've crafted this analogous example:
Imagine that we have a Timer, called timer1 which has a Timer1_Tick event set up. Now, the only action I take on that event is to update the UI with the current date time.
private void Timer1_Tick(object sender, EventArgs e)
{
txtTimerValue.Text = DateTime.Now.ToString("hh:mm:ss.FFF", CultureInfo.InvariantCulture);
}
This is simple enough, my UI updates every few hundredths of seconds and I can happily watch time go by.
Now imagine that I also want to also calculate the first 500 prime numbers in the same method like so:
private void Timer1_Tick(object sender, EventArgs e)
{
txtTimerValue.Text = DateTime.Now.ToString("hh:mm:ss.FFF", CultureInfo.InvariantCulture);
List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500);
PrintPrimeNumbersToScreen(primeNumbersList);
}
private List<int> WorkOutFirstNPrimeNumbers(int n)
{
List<int> primeNumbersList = new List<int>();
txtPrimeAnswers.Clear();
int counter = 1;
while (primeNumbersList.Count < n)
{
if (DetermineIfPrime(counter))
{
primeNumbersList.Add(counter);
}
counter++;
}
return primeNumbersList;
}
private bool DetermineIfPrime(int n)
{
for (int i = 2; i < n; i++)
{
if (n % i == 0)
{
return false;
}
}
return true;
}
private void PrintPrimeNumbersToScreen(List<int> primeNumbersList)
{
foreach (int primeNumber in primeNumbersList)
{
txtPrimeAnswers.Text += String.Format("The value {0} is prime \r\n", primeNumber);
}
}
This is when I experience the problem. The intensive method that calculates the prime numbers blocks the event handler from being run - hence my timer text box now only updates every 30 seconds or so.
My question is, how can I resolve this while observing the following rules:
I have tried to do some things with async/await and making my prime number calculation function return a Task> but haven't managed to resolve my problem. The await call in the Timer1_Tick event still seems to block, preventing further execution of the handler.
Any help would be gladly appreciated - I'm very good at accepting correct answers :)
Update: I am very grateful to @sstan who was able to provide a neat solution to this problem. However, I'm having trouble applying this to my real Kinect-based situation. As I am a little concerned about making this question too specific, I have posted the follow up as a new question here: Kinect Frame Arrived Asynchronous
So you want to start a Task without waiting for the result. When the task has finished calculating it should update the UI.
The reason that your UI isn't responsive during the long action is because you didn't declare your event handler async. The easiest way to see the result of this is by creating an event handler for a button:
synchronous - UI is blocked during execution:
private void Button1_clicked(object sender, EventArgs e)
{
List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500);
PrintPrimeNumbersToScreen(primeNumbersList);
}
asynchronous - UI is responsive during execution:
private async void Button1_clicked(object sender, EventArgs e)
{
List<int> primeNumbersList = await Task.Run( () => WorkOutFirstNPrimeNumbers(500));
PrintPrimeNumbersToScreen(primeNumbersList);
}
Note the differences:
Note:
Task<TResult
> instead of TResult
and Task
instead of void
.await Task<TResult
> is a TResult
. The return value of await Task
is a void
.Task.Run ( () => MyFunction(...))
Task.Run
is an awaitable Task.Task
or Task<TResult
>.The problem is that your timer is faster than your calculations. What do you want if a new tick is reported when the previous calculations are not finished?
(1) Start the Task, but do not await for it.
private void Button1_clicked(object sender, EventArgs e)
{
Task.Run ( () =>
{
List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500);
PrintPrimeNumbersToScreen(primeNumbersList);
});
}
(2) ignore the tick if the task is still busy:
Task primeCalculationTask = null;
private void Button1_clicked(object sender, EventArgs e)
{
if (primeCalculationTask == null || primeCalculationTask.IsCompleted)
{ // previous task finished. Stat a new one
primeCalculationTask = Task.Run ( () =>
{ List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500);
PrintPrimeNumbersToScreen(primeNumbersList);
});
}
}
(3) Start a task that calculates continuously
private void StartTask(CancellationToken token)
{
Task.Run( () =>
{
while (!token.IsCancelRequested)
{
List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500);
PrintPrimeNumbersToScreen(primeNumbersList);
}
})
}