异步等待保持事件发生

Ben*_*ggs 5 c# asynchronous async-await

我在C#.NET应用程序中有关于async\await的问题.我实际上试图在基于Kinect的应用程序中解决这个问题,但为了帮助我说明,我已经制作了这个类似的例子:

想象一下,我们有一个名为timer1的Timer,它设置了Timer1_Tick事件.现在,我对该事件采取的唯一操作是使用当前日期时间更新UI.

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

这很简单,我的UI每隔几百分钟更新一次,我很高兴看到时间流逝.

现在想象一下,我也想用同样的方法计算前500个素数,如下:

 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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是我遇到问题的时候.计算素数的密集方法会阻止事件处理程序运行 - 因此我的计时器文本框现在每30秒左右更新一次.

我的问题是,如何在遵守以下规则的同时解决这个问题:

  • 我需要我的UI计时器文本框像以前一样平滑,可能是通过将密集素数计算推送到不同的线程.我想,这会使事件处理程序像以前一样频繁运行,因为阻塞语句不再存在.
  • 每次素数计算功能结束时,都会将结果写入屏幕(使用我的PrintPrimeNumbersToScreen()函数),并且应该立即重新开始,以防万一这些素数当然发生变化.

我试图用async/await做一些事情并让我的素数计算函数返回一个Task>但是还没有设法解决我的问题.Timer1_Tick事件中的await调用似乎仍然阻塞,阻止了进一步执行处理程序.

任何帮助都会很高兴 - 我非常擅长接受正确的答案:)

更新:我非常感谢@sstan能够为这个问题提供一个简洁的解决方案.但是,我在将这个应用到基于Kinect的真实情况时遇到了麻烦.由于我有点担心这个问题太具体,我在这里发布了一个新的问题:Kinect Frame Arrived Asynchronous

Har*_*lse 1

所以你想启动一个任务而不等待结果。当任务完成计算后,它应该更新 UI。

首先是关于 async-await 的一些事情,然后是你的答案

您的 UI 在长时间操作期间没有响应的原因是您没有声明事件处理程序异步。查看结果的最简单方法是为按钮创建事件处理程序:

同步 - UI 在执行期间被阻止:

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

异步 - UI 在执行期间响应:

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

注意差异:

  • 该函数被声明为异步
  • 使用 Task.Run 启动任务,而不是调用该函数
  • wait 语句确保您的 UI 线程返回并继续处理所有 UI 请求。
  • 一旦任务完成,UI 线程就会继续等待的下一部分。
  • wait 的值是 WorkOutFirstNPrimeNumber 函数的返回值

笔记:

  • 通常你会看到异步函数返回一个 Task 而不是 void,返回 Task <TResult> 而不是 TResult。
  • wait Task 是一个 void,await Task <TResult> 是一个 TResult。
  • 要将函数作为单独的任务启动,请使用 Task.Run ( () => MyFunction(...))
  • Task.Run的返回是一个可等待的Task。
  • 每当你想使用await时,你都必须声明你的函数async,从而返回Task或Task <TResult>。
  • 所以你的调用者必须是异步的等等。
  • 唯一可能返回 void 的异步函数是事件处理程序。

您的问题:计算仍然繁忙时报告计时器滴答声

问题是你的计时器比你的计算快。如果前面的计算还没有完成就报告了新的tick,你想要什么?

  1. 无论如何,开始新的计算。这可能会导致许多线程同时进行计算。
  2. 忽略勾选,直到没有计算繁忙
  3. 您还可以选择只让一个任务进行计算,并在完成后立即启动它们。在这种情况下,计算会连续运行

(1) 启动任务,但不要等待它。

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

(2) 如果任务仍然繁忙,则忽略该勾号:

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);
        }); 
    }
}
Run Code Online (Sandbox Code Playgroud)

(3)启动一个连续计算的任务

private void StartTask(CancellationToken token)
{
    Task.Run( () =>
    {
        while (!token.IsCancelRequested)
        {
            List<int> primeNumbersList = WorkOutFirstNPrimeNumbers(500);
            PrintPrimeNumbersToScreen(primeNumbersList);
        }
     })
 }
   
Run Code Online (Sandbox Code Playgroud)