简体   繁体   English

C# ASP.NET Core 一劳永逸

[英]C# ASP.NET Core Fire and forget

I have a sync controller method我有一个同步控制器方法

    public IActionResult Action(int num)
    {
        //operations-1

        Task.Run(() => DoWork(num));

        //operations-2
        int a = num + 123;
        return Ok(a);
    }

and DoWork method和 DoWork 方法

    private bool DoWork(int num)
    {
        //operations

        return true;
    }

What I'm trying to do is to run DoWork method in background when calling that endpoint from Postman, but I want to get result in Postman and then debug DoWork method (from breakpoint in DoWork method) - is it possible?我想要做的是在从 Postman 调用该端点时在后台运行 DoWork 方法,但我想在 Postman 中获得结果,然后调试 DoWork 方法(从 DoWork 方法中的断点) - 可能吗?

For that moment, controller action and DoWork() are executing simultaneously but when I reach在那一刻,控制器动作和 DoWork() 正在同时执行,但是当我到达

return Ok(a);

applications waits for DoWork instead of returning value.应用程序等待 DoWork 而不是返回值。 I have tried also我也试过

Task.Factory.StartNew(() => DoWork());
ThreadPool.QueueUserWorkItem(o => DoWork());

but result is the same.但结果是一样的。

I want DoWork method to return value but that value is not neccessary by controller action method - it will be used in different place, not connected with that.我希望 DoWork 方法返回值,但控制器操作方法不需要该值 - 它将在不同的地方使用,而不是与之连接。

Tasks are high level threads that make sure you are not blocking any context.任务是确保您没有阻塞任何上下文的高级线程

You either want to use something like RabbitMQ or IHostedService from ASP.NET Core 2.0 to do fire and forget task that kick in after a request has completed.您要么想使用 ASP.NET Core 2.0 中的RabbitMQIHostedService 之类的东西来执行触发并忘记在请求完成后启动的任务。

If you use Db in project, you can use Hangfire It is easy to use background process manager.如果在项目中使用 Db,则可以使用 Hangfire 后台进程管理器,非常好用。 https://www.hangfire.io/ https://www.hangfire.io/

you can use it very easyly like BackgroundJob.Enqueue(() => DoWork(num));您可以像BackgroundJob.Enqueue(() => DoWork(num));一样轻松使用它

Quite frankly, this looks like an XY problem . 坦率地说,这看起来像是XY问题 Performing a fire and forget call in a controller doesn't seem to make much sense. 在控制器中执行火灾并忘记呼叫似乎没有多大意义。 If you know your operation will take some time, it probably would be a better idea to use a queue and a dequeuer webjob. 如果您知道您的操作将花费一些时间,那么使用队列和出队Webjob可能是一个更好的主意。

That being said, what you want to achieve is possible: your DoWork method simply has to be async . 话虽如此,您想要实现的目标是可能的:您的DoWork方法只需要async

public IActionResult Action(int num)
{
    //operations-1
    Task.Run(() => DoWork(num)); // async call not awaited

    //operations-2
    int a = num + 123;
    return Ok(a);
}

public async Task<bool> DoWork(int num)
{
    await Task.Delay(10000);
    var i = 9; // breakpoint will hit after 10s
    return true;
}

Use a background queue sometimes is overkill.有时使用后台队列是矫枉过正的。 There are a number of sites showing a way to do when you need to access the database context.当您需要访问数据库上下文时,有许多站点提供了一种方法。 The problem of Task.Run in a controller, is that the spawned task cannot access the same context as the controller uses as it may (probably will) get disposed before that Task accesses it. Task.Run 在控制器中的问题在于,生成的任务无法访问与控制器使用的上下文相同的上下文,因为它可能(可能会)在该任务访问它之前被释放。 You can get round this by ensuring that the sub task only references Dependencies it knows will stay alive, either by using a singleton service or better for database context, using the IServiceScopeFactory .您可以通过确保子任务仅引用它知道将保持活动状态的依赖项来解决此问题,无论是通过使用单例服务还是更好地用于数据库上下文,使用IServiceScopeFactory

The crux of this is to create a seperate dependency that can handle your database context or Repository.关键是创建一个单独的依赖项来处理您的数据库上下文或存储库。 This can be injected into your controller as normal.这可以像往常一样注入到您的控制器中。

    public void Execute(Func<IRepository, Task> databaseWork)
    {
        // Fire off the task, but don't await the result
        Task.Run(async () =>
        {
            // Exceptions must be caught
            try
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var repository = scope.ServiceProvider.GetRequiredService<IRepository>();
                await databaseWork(repository);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        });
    }

Then call this from your controller such as然后从您的控制器调用它,例如

        // Delegate the slow task another task on the threadpool
        _fireForgetRepositoryHandler.Execute(async repository =>
        {
            // Will receive its own scoped repository on the executing task
            await repository.DoLOngRunningTaskAsync();;
        });

Note: This builds upon Adem Catamak's answer.注意:这建立在 Adem Catamak 的回答之上。

Hangfire can be used, but no actual database is required because it can work with memory storage :可以使用 Hangfire,但不需要实际的数据库,因为它可以使用内存存储

services.AddHangfire(opt => opt.UseMemoryStorage());
JobStorage.Current = new MemoryStorage();

While it has some overhead, Hangfire allows managing these jobs as opposed to having stuff running async and requiring custom code for simple things like run time, unhandled exceptions, custom code for DI support.虽然它有一些开销,但 Hangfire 允许管理这些作业,而不是让东西运行异步并且需要自定义代码来处理简单的事情,例如运行时、未处理的异常、用于 DI 支持的自定义代码。

Credit: Codidact学分:抄本

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM