【问题标题】:Web Api - Fire and ForgetWeb Api - 一劳永逸
【发布时间】:2016-07-20 00:31:07
【问题描述】:

我有一个 Web API 操作,我需要在其中运行一些任务并忘记这个任务。 这就是我的方法现在的组织方式:

public async Task<SomeType> DoSth()
{
    await Task.Run(...);
    .....
    //Do some other work
}

问题是,显然它在等待完成后停止在等待线等待,然后才继续工作。 我需要“开火然后忘记” 我应该在没有任何异步等待的情况下直接调用 Task.Run() 吗?

【问题讨论】:

标签: c# rest asp.net-web-api task-parallel-library


【解决方案1】:

为了调用fire and forget WebApi 方法,我使用了以下代码确保它返回OK 响应。我的情况是,登录时创建的不记名授权令牌存储在 cookie 中:

...
FireAndForget().Wait();
...

private async Task FireAndForget()
    {
        using (var httpClient = new HttpClient())
        {
            HttpCookie cookie = this.Request.Cookies["AuthCookieName"];
            var authToken = cookie["AuthTokenPropertyName"] as string;
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
            using (var response = await httpClient.GetAsync("http://localhost/api/FireAndForgetApiMethodName"))
            {
                //will throw an exception if not successful
                response.EnsureSuccessStatusCode();
            }
        }
    }

【讨论】:

    【解决方案2】:

    我使用HangFire。

    这对我来说是最好的。

    在 .NET 和 .NET Core 应用程序中执行后台处理的简单方法。无需 Windows 服务或单独的进程。

    以持久存储为后盾。开放且免费用于商业用途。

    【讨论】:

      【解决方案3】:

      我同意其他人的观点,即您不应该忘记您的电话。但是,为了回答您的问题,如果您从 Task.Run() 行中删除 await,调用将不会被阻塞,如图所示 here

      public async Task<SomeType> DoSth()
      {
          Task.Run(...);
          .....
          //Do some other work while Task.Run() continues in parallel.
      }
      

      【讨论】:

      • 有时候忘记一个电话也很好。例如,如果您在 IOT 应用中发送心跳信号,您通常不会关心响应。
      【解决方案4】:

      而我需要“一劳永逸”

      我有一篇博文详细介绍了fire-and-forget on ASP.NET 的几种不同方法。

      总结:首先,尽量不要做一劳永逸的事情。这几乎总是一个坏主意。你真的想“忘记”吗?如,不在乎它是否成功完成?忽略任何错误?在没有任何日志通知的情况下接受偶尔的“丢失工作”?几乎总是,答案是否定的,即发即弃不是合适的方法。

      一个可靠的解决方案是构建一个合适的分布式架构。也就是说,构造一条表示要完成的工作的消息并将该消息排队到可靠队列(例如,Azure 队列、MSMQ 等)。然后有一个独立的后端来处理该队列(例如,Azure WebJob、Win32 服务等)。

      我应该只调用 Task.Run() 而不使用任何 async-await 吗?

      没有。这是最糟糕的解决方案。如果您必须进行即发即弃,并且不愿意构建分布式架构,那么请考虑 Hangfire。如果这对您不起作用,那么至少非常您应该通过HostingEnvironment.QueueBackgroundWorkItem 或我的ASP.NET Background Tasks library 在ASP.NET 运行时注册您的牛仔背景工作。请注意,QBWI 和 AspNetBackgroundTasks 都是不可靠的解决方案;他们只是最小化你失去工作的机会,而不是防止它。

      【讨论】:

      • 如果“即发即忘”任务对 Web api 功能不重要,Task.Run() 是否可以接受?例如。查找附加日志记录的值。另外,如果我知道应用程序池不会那么频繁地自行回收?
      • @sgarg:你为什么不使用HostingEnvironment.QueueBackgroundWorkItem?它同样易于使用,并且至少比Task.Run 安全一点。你仍然不应该将它用于任何关键的事情,但可以肯定的是,不重要的工作可以去那里。附带说明一下,您不知道应用程序池将多久回收一次 - IIRC,它是每 19 小时默认情况下,但如果 ASP.NET 认为您的应用程序不是,它会更频繁地执行此操作t 工作正常。
      • 我不喜欢人们说“即发即弃”几乎总是一个坏主意的观点,但我同意这个警告。就我而言,我会预先进行请求和记录,如果任务成功,用户会收到通知,或者工作会通知他们是否失败。他们还可以检查状态,如果没有成功,他们将被禁止继续,他们必须重试。为什么这是个坏主意?只要设计到位,就可以解决故障问题。
      • @Mark:ASP.NET 上的即发即弃本质上是不可靠的。您的用户可能永远不会收到刚刚消失的作业的成功或失败通知。底线:ASP.NET 偶尔会回收您的进程(等待现有请求完成),因此如果您的进程在请求之外进行工作,它可能会突然消失。
      • @Mark:抱歉,这正是我们在这里讨论的场景。 适当的(阅读:只有真正可靠的)解决方案是使用具有独立后端进程的可靠队列。您可以查看 Hangfire,它试图降低进入门槛(通过权衡)。
      【解决方案5】:

      真正的“一劳永逸”任务在 asp.net 中可能很困难,因为它们通常会随着创建它们的请求而死亡。

      如果您使用的是 4.5.2+,那么您可以使用 QueueBackgroundWorkItem 来运行任务。通过这种方法注册任务,AppDomain 将尝试延迟关闭,直到它们全部完成,但仍有可能在它们完成之前被杀死。这可能是最简单的事情,但值得一读,以了解哪些实例会导致作业被取消。

      HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
      {
        await Task.Run(...);
      });
      

      有一个名为hangfire 的工具使用持久存储来确保任务已完成并具有内置的重试和错误记录功能。这更适用于“后台任务”,但确实适合一劳永逸。这相对容易设置并提供各种后备存储,我不记得确切的细节,但有些需要许可证,有些不需要(如 MSSQL)。

      【讨论】:

      • fire and forget in ASP.NET WebForms 和 windows.close() ?
      • 为什么任务“随着作为它们的一部分而创建的请求一起死亡”?
      【解决方案6】:

      为了一劳永逸,使用这个

      Task.Factory.StartNew(async () =>
      {
          using (HttpClient client = new HttpClient())
          {
              await client.PostAsync("http://localhost/api/action", new StringContent(""));
          }
      });
      

      【讨论】:

      【解决方案7】:

      永远不要忘记,因为那样你就不会看到任何错误,如果出现问题,这会导致一些非常尴尬的故障排除(不能保证让任务方法自己处理异常,因为任务可能一开始就无法成功启动)。除非您真的不介意任务是否执行任何操作,但这很不寻常(因为,如果您真的不在乎,为什么首先运行任务)?至少,使用continuation 创建您的任务:

      Task.Run(...)
        .ContinueWith(t => 
          logException(t.Exception.GetBaseException()),
          TaskContinuationOptions.OnlyOnFaulted
        )
      ;
      

      您可以根据需要将其变得更复杂。

      在 Web API 的特定情况下,您实际上可能希望在完成请求之前等待后台任务完成。如果你不这样做,你就会在后台运行一些东西,这可能会歪曲你的服务实际可以承受多少负载,或者如果客户端发出太多请求而你没有采取任何措施来限制它们,甚至会完全停止工作。您可以收集任务并在最后发出await Task.WhenAll(...) 来实现这一目标;这样一来,您就可以在后台任务繁重的同时继续做有用的工作,但在一切完成之前您不会返回。

      【讨论】:

      • 我想触发并忘记,因为我有可以获取进程状态的操作。这对我来说并不重要,因为首先完成基本的数据库更新,然后运行任务。如果任务失败,用户将无法继续,因为数据库没有更新,他们将被要求重试。
      猜你喜欢
      • 2017-06-16
      • 1970-01-01
      • 2016-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-11
      • 1970-01-01
      相关资源
      最近更新 更多