Async is waiting to hold events

I have a question about async \ await in a C # .NET application. I'm actually trying to solve this problem in a Kinect based application, but to help me illustrate this, I created this similar example:

Imagine that we have a timer called timer 1 in which the Timer1_Tick event is set. Now, the only action I take on this event is updating the user interface with the current time.

private void Timer1_Tick(object sender, EventArgs e) { txtTimerValue.Text = DateTime.Now.ToString("hh:mm:ss.FFF", CultureInfo.InvariantCulture); } 

It's simple enough, my user interface is updated every few hundredths of a second, and I can enjoy watching the time.

Now imagine that I also want to calculate the first 500 primes in the same method:

  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 this problem. An intensive method that calculates prime numbers blocks the start of the event handler, so the timer text box is now updated only every 30 seconds.

My question is how can I solve this by following these rules:

  • I need the UI timer text box to be as smooth as before, perhaps by pushing the computation of an intensive prime number to another stream. I think this will allow the event handler to work as often as before because the lock statement no longer exists.
  • Every time a function for calculating primes ends, it should be written to the screen (using my PrintPrimeNumbersToScreen () function), and it should be started immediately again, just in case these primes change, of course.

I tried to do some things with async / await and force the function to calculate the number of primes to return the> task, but I was not able to solve my problem. Waiting for a call in the Timer1_Tick event is still blocked, which prevents further execution of the handler.

Any help would be appreciated - I accept the correct answers very well :)

Update: I am very grateful to @sstan, who was able to provide a neat solution to this problem. However, I am having problems applying this in my actual Kinect situation. Since I am a little worried about the question that this question is too specific, I posted the following question: Kinect Frame arrived asynchronously

+5
source share
3 answers

So, you want to run the task without waiting for the result. When the task completes the calculation, it must update the user interface.

First, some things about asynchronous wait, later your answer

The reason your user interface is not responding during a long action is because you did not declare an async event handler. The easiest way to see the result of this is to create an event handler for the button:

synchronously - the user interface is blocked at runtime:

 private void Button1_clicked(object sender, EventArgs e) { List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500); PrintPrimeNumbersToScreen(primeNumbersList); } 

asynchronous - the user interface responds at runtime:

 private async void Button1_clicked(object sender, EventArgs e) { List<int> primeNumbersList = await Task.Run( () => WorkOutFirstNPrimeNumbers(500)); PrintPrimeNumbersToScreen(primeNumbersList); } 

Please note the differences:

  • Function declared async
  • Instead of calling a function, Task is launched using Task.Run
  • a wait expression confirms that your user interface returns and continues to process all user interface requests.
  • After completing the task, the user interface flow continues with the next part of the wait.
  • the wait value is the return of the WorkOutFirstNPrimeNumber function

Note:

  • usually you will see that async functions return a task instead of void and Task <TResult > instead of TResult.
  • Expectation. The task is emptiness and expectation. The task <TResult > is TResult.
  • To run a function as a separate task, use Task.Run (() => MyFunction (...))
  • Return Task.Run is the expected task.
  • Whenever you want to use await, you need to declare your async function and thus return Task or Task <TResult >.
  • Thus, your subscribers should be asynchronous, etc.
  • The only async function that can return void is an event handler.

Your problem: timer tick when calculations are still busy

The problem is that your timer is faster than your calculations. What do you need if a new tick is reported when previous calculations are not completed?

  • Start new calculations. This can cause many threads to perform calculations at the same time.
  • Ignore the checkmark until the calculations are busy.
  • You can also allow only one task to perform calculations and run them as soon as they are completed. In this case, the calculations are performed continuously

(1) Run the task, but do not wait for it.

 private void Button1_clicked(object sender, EventArgs e) { Task.Run ( () => { List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500); PrintPrimeNumbersToScreen(primeNumbersList); }); } 

(2) ignore the checkmark 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 Task.Run ( () => { List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500); PrintPrimeNumbersToScreen(primeNumbersList); }); } } 

(3) Run a task that continuously calculates

 private void StartTask(CancellationToken token) { Task.Run( () => { while (!token.IsCancelRequested) { List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500); PrintPrimeNumbersToScreen(primeNumbersList); } }) } 
+1
source

It may not be the best solution, but it will work. You can create 2 separate timers. Your first Timer Tick event handler should deal with your txtTimerValue text box. It can remain as it was originally:

 private void Timer1_Tick(object sender, EventArgs e) { txtTimerValue.Text = DateTime.Now.ToString("hh:mm:ss.FFF", CultureInfo.InvariantCulture); } 

For your second Tick timer event handler, define a Tick event handler as follows:

 private async void Timer2_Tick(object sender, EventArgs e) { timer2.Stop(); // this is needed so the timer stops raising Tick events while this one is being awaited. txtPrimeAnswers.Text = await Task.Run(() => { List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500); return ConvertPrimeNumbersToString(primeNumbersList); }); timer2.Start(); // ok, now we can keep ticking. } private string ConvertPrimeNumbersToString(List<int> primeNumbersList) { var primeNumberString = new StringBuilder(); foreach (int primeNumber in primeNumbersList) { primeNumberString.AppendFormat("The value {0} is prime \r\n", primeNumber); } return primeNumberString.ToString(); } // the rest of your methods stay the same... 

You will notice that I changed your PrintPrimeNumbersToScreen() method to ConvertPrimeNumbersToString() (the rest has not changed). The reason for the change is that you really want to minimize the amount of work performed in the user interface thread. Therefore, it is best to prepare a string from a background thread, and then simply perform a simple assignment of the txtPrimeAnswers text field in the user interface thread.

EDIT: another alternative that can be used with a single timer

Here is another idea, but with one timer. The idea here is that your Tick handler will continue to perform regular updates and update the text box with a timer each time. But, if prime numbers are already being calculated in the background, the event handler will simply skip this part. Otherwise, it will start calculating prime numbers and update the text box when it is done.

 // global variable that is only read/written from UI thread, so no locking is necessary. private bool isCalculatingPrimeNumbers = false; private async void Timer1_Tick(object sender, EventArgs e) { txtTimerValue.Text = DateTime.Now.ToString("hh:mm:ss.FFF", CultureInfo.InvariantCulture); if (!this.isCalculatingPrimeNumbers) { this.isCalculatingPrimeNumbers = true; try { txtPrimeAnswers.Text = await Task.Run(() => { List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500); return ConvertPrimeNumbersToString(primeNumbersList); }); } finally { this.isCalculatingPrimeNumbers = false; } } } private string ConvertPrimeNumbersToString(List<int> primeNumbersList) { var primeNumberString = new StringBuilder(); foreach (int primeNumber in primeNumbersList) { primeNumberString.AppendFormat("The value {0} is prime \r\n", primeNumber); } return primeNumberString.ToString(); } // the rest of your methods stay the same... 
+2
source

You should avoid using async / await (no matter how good they are), since the Microsoft Reactive Framework (Rx) - NuGet or "Rx-WinForms" or "Rx-WPF" is a much better approach.

This is the code you need to solve Windows Forms:

 private void Form1_Load(object sender, EventArgs e) { Observable .Interval(TimeSpan.FromSeconds(0.2)) .Select(x => DateTime.Now.ToString("hh:mm:ss.FFF", CultureInfo.InvariantCulture)) .ObserveOn(this) .Subscribe(x => txtTimerValue.Text = x); txtPrimeAnswers.Text = ""; Observable .Interval(TimeSpan.FromSeconds(0.2)) .Select(n => (int)n + 1) .Where(n => DetermineIfPrime(n)) .Select(n => String.Format("The value {0} is prime\r\n", n)) .Take(500) .ObserveOn(this) .Subscribe(x => txtPrimeAnswers.Text += x); } 

What is it. Very simple. All this happens in the background thread before being reconfigured back to the user interface.

The above should be explanatory enough, but scream if you need any further explanation.

+2
source

All Articles