如何在后台运行C#任务以防止UI阻塞? [英] How to run a C# task in background so the UI is not blocked?

查看:53
本文介绍了如何在后台运行C#任务以防止UI阻塞?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个ASP.NET MVC Web应用程序.

I have an ASP.NET MVC web application.

在某个时候,UI用户对服务器进行POST.服务器必须在不同的线程中执行一些繁重的操作,并尽快将响应返回给用户.

At a certain point, the UI user makes a POST to the server. The server has to do some heavy operations in a different thread, and return a response to the user as soon as possible.

发送回UI的响应与繁琐操作的结果无关,因此在繁琐的操作完成之前,无需阻塞UI .该POST方法的行为应类似于某些大型计算工作的触发器.应立即通知用户服务器已开始执行繁重的操作.

The response sent back to the UI does NOT depend on the result of the heavy operations, so the UI does not need to be blocked till the heavy operations are done. This POST method should behave like a trigger for some big computational stuff. The user should be notified immediately that the server has started working on the heavy operations.

应该发生的情况的骨架是:

A skeleton for what should happen is:

[HttpPost]
public ActionResult DoSomething(PostViewModel model)
{
    //////////////////////////
    /*
        * The code in this section should run asynchronously (in another thread I guess).
        * That means the UI should not wait for any of these operations to end.
        * 
        * */
    ComputeHeavyOperations();
    //////////////////////////


    //the response should be returned immediatelly 
    return Json("Heavy operations have been triggered.");
}

private void ComputeHeavyOperations()
{
    //execute some heavy operations; like encoding a video 
}

我该如何实现这样的东西?

How can I implement something like this?

推荐答案

您可以使用排队的后台任务并实现 BackgroundService .此链接很有用.

You can use Queued background tasks and implement BackgroundService. This link is useful.

public class BackgroundTaskQueue : IBackgroundTaskQueue
    {
        private readonly ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
            new ConcurrentQueue<Func<CancellationToken, Task>>();
        private readonly SemaphoreSlim _signal = new SemaphoreSlim(0);

        public void QueueBackgroundWorkItem(
            Func<CancellationToken, Task> workItem)
        {
            if (workItem == null)
            {
                throw new ArgumentNullException(nameof(workItem));
            }

            _workItems.Enqueue(workItem);
            _signal.Release();
        }

        public async Task<Func<CancellationToken, Task>> DequeueAsync(
            CancellationToken cancellationToken)
        {
            await _signal.WaitAsync(cancellationToken);
            _workItems.TryDequeue(out var workItem);

            return workItem;
        }
    }

QueueHostedService 中,队列中的后台任务出队并作为

In QueueHostedService, background tasks in the queue are dequeued and executed as a BackgroundService, which is a base class for implementing a long running IHostedService:

public class QueuedHostedService : BackgroundService
    {
        private readonly ILogger _logger;

        public QueuedHostedService(IBackgroundTaskQueue taskQueue,
            ILoggerFactory loggerFactory)
        {
            TaskQueue = taskQueue;
            _logger = loggerFactory.CreateLogger<QueuedHostedService>();
        }

        public IBackgroundTaskQueue TaskQueue { get; }

        protected override async Task ExecuteAsync(
            CancellationToken cancellationToken)
        {
            _logger.LogInformation("Queued Hosted Service is starting.");

            while (!cancellationToken.IsCancellationRequested)
            {
                var workItem = await TaskQueue.DequeueAsync(cancellationToken);

                try
                {
                    await workItem(cancellationToken);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex,
                        $"Error occurred executing {nameof(workItem)}.");
                }
            }

            _logger.LogInformation("Queued Hosted Service is stopping.");
        }
    }

服务已在 Startup.ConfigureServices 中注册. IHostedService 实现通过 AddHostedService 扩展方法注册:

The services are registered in Startup.ConfigureServices. The IHostedService implementation is registered with the AddHostedService extension method:

services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

在控制器中: IBackgroundTaskQueue 被注入到构造函数中并分配给Queue.注入 IServiceScopeFactory 并将其分配给 _serviceScopeFactory .该工厂用于创建 IServiceScope 的实例,该实例用于在范围内创建服务.创建作用域是为了使用应用程序的 AppDbContext (作用域服务)在 IBackgroundTaskQueue (单例服务)中写入数据库记录.

In the controller : The IBackgroundTaskQueue is injected into the constructor and assigned to Queue. An IServiceScopeFactory is injected and assigned to _serviceScopeFactory. The factory is used to create instances of IServiceScope, which is used to create services within a scope. A scope is created in order to use the app's AppDbContext (a scoped service) to write database records in the IBackgroundTaskQueue (a singleton service).

public class SomeController : Controller
{
    private readonly AppDbContext _db;
    private readonly ILogger _logger;
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public SomeController(AppDbContext db, IBackgroundTaskQueue queue, 
        ILogger<SomeController> logger, IServiceScopeFactory serviceScopeFactory)
    {
            _db = db;
            _logger = logger;
            Queue = queue;
            _serviceScopeFactory = serviceScopeFactory;
    }

    public IBackgroundTaskQueue Queue { get; }

    [HttpPost]
    public ActionResult DoSomething(PostViewModel model)
    {
        //////////////////////////
        /*
            * The code in this section should run asynchronously (in another thread I guess).
            * That means the UI should not wait for any of these operations to end.
            * 
            * */
        ComputeHeavyOperations();
        //////////////////////////


        //the response should be returned immediatelly 
        return Json("Heavy operations have been triggered.");
    }

    private void ComputeHeavyOperations()
    {
        Queue.QueueBackgroundWorkItem(async token =>
        {
            using (var scope = _serviceScopeFactory.CreateScope())
            {
                var scopedServices = scope.ServiceProvider;
                var db = scopedServices.GetRequiredService<AppDbContext>();

                    try
                    {
                        //use db to crud operation on database
                        db.doSomeThingOnDatabase();
                        await db.SaveChangesAsync();
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, 
                            "An error occurred writing to the " +
                            $"database. Error: {ex.Message}");
                    }

                    await Task.Delay(TimeSpan.FromSeconds(5), token);        

      }
        _logger.LogInformation(
            "some background task have done on database successfully!");
    });
} }

这篇关于如何在后台运行C#任务以防止UI阻塞?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆