定期以指定的时间间隔运行异步方法

时间:2015-05-26 14:54:51

标签: c# asp.net .net multithreading async-await

我需要从C#Web应用程序向服务发布一些数据。用户使用应用程序时会收集数据本身(一种使用情况统计信息)。我不希望在每个用户的请求期间向服务发送数据,我宁愿在应用程序中收集数据,然后在一个单独的线程中发送单个请求中的所有数据,这不会满足用户的请求(我的意思是用户不必等待服务处理请求)。为此,我需要一种JS的setInterval模拟 - 每隔X秒启动一次函数,将所有收集的数据刷新到服务中。

我发现Timer类提供了一些类似的(Elapsed事件)。但是,这允许只运行一次方法,但这不是一个大问题。它的主要困难是它需要签名

void MethodName(object e, ElapsedEventArgs args)

虽然我想启动异步方法,但会调用web服务(输入参数并不重要):

async Task MethodName(object e, ElapsedEventArgs args)

有人可以建议如何解决所描述的任务吗?任何提示赞赏。

3 个答案:

答案 0 :(得分:21)

async等效项是while循环Task.Delay(内部使用System.Threading.Timer):

public async Task PeriodicFooAsync(TimeSpan interval, CancellationToken cancellationToken)
{
    while (true)
    {
        await FooAsync();
        await Task.Delay(interval, cancellationToken)
    }
}

传递CancellationToken非常重要,这样您就可以在需要时停止该操作(例如关闭应用程序时)。

现在,虽然这通常与.Net相关,但在ASP.Net中,任何类型的火灾都会发生危险而忘记。有几个解决方案(如HangFire),Fire and Forget on ASP.NET by Stephen Cleary中的How to run Background Tasks in ASP.NET by Scott Hanselman其他人记录了一些

答案 1 :(得分:4)

这样做的简单方法是使用Tasks和一个简单的循环:

public async Task StartTimer(CancellationToken cancellationToken)
{

   await Task.Run(async () =>
   {
      while (true)
      {
          DoSomething();
          await Task.Delay(10000, cancellationToken);
          if (cancellationToken.IsCancellationRequested)
              break;
      }
   });

}

当您想要停止线程时,只需中止令牌:

cancellationToken.Cancel();

答案 2 :(得分:2)

以下是一种以周期性方式调用异步方法的方法:

public static async Task PeriodicAsync(Func<Task> taskFactory, TimeSpan interval,
    CancellationToken cancellationToken = default)
{
    while (true)
    {
        var delayTask = Task.Delay(interval, cancellationToken);
        await taskFactory();
        await delayTask;
    }
}

所提供的taskFactoryinterval被调用,然后等待创建的Task。等待的持续时间不会影响间隔,除非间隔时间更长。在这种情况下,no-overlaping-execution的主体优先,因此将延长等待时间以匹配等待时间。

在发生异常的情况下,PeriodicAsync任务将失败,因此,如果您希望它具有弹性,则应该在taskFactory内包含严格的错误处理。

用法示例:

Task statisticsUploader = PeriodicAsync(async () =>
{
    try
    {
        await UploadStatisticsAsync();
    }
    catch (Exception ex)
    {
        // Log the exception
    }
}, TimeSpan.FromMinutes(5));