首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >如何在asp.net webapi中只有一个线程来触发和忘记任务?

如何在asp.net webapi中只有一个线程来触发和忘记任务?
EN

Stack Overflow用户
提问于 2021-12-20 21:58:17
回答 1查看 275关注 0票数 0

在我的asp.net webapi (框架.Net 4.7.2)中,我需要调用Redis (使用StackExchange.Redis),以便删除火灾中的密钥并忘记,我正在进行一些压力测试。

当我比较不同的方式来获得最大的速度:

  • 我已经用FireAndForget标志测试了执行命令,
  • 我还测量了一个简单的命令到Redis,等待它。
  • 现在,我正在寻找一种方法来收集在15 to窗口中接收到的命令列表,然后通过流水线方式一次执行所有命令。

我第一次尝试用Task.Run动作给Redis打电话,但我观察到的问题是,在压力下,我的webapi的记忆一直在上升。内存中满是带有折叠代码的System.Threading.IThreadPoolWorkItem[]对象:

代码语言:javascript
代码运行次数:0
运行
复制
    [HttpPost]
    [Route("api/values/testpostfireforget")]
    public  ApiResult<int> DeleteFromBasketId([FromBody] int basketId)
    {

        var response = new DeleteFromBasketResponse<int>();

        var cpt = Interlocked.Increment(ref counter);

            Task.Run(async () => {
                await db.StringSetAsync($"BASKET_TO_DELETE_{cpt}",cpt.ToString())
                         .ConfigureAwait(false); 
            });  
           return response;
     }

因此,我认为,在压力下,我的api一直在内存中执行后台任务,并尽可能快地执行它们,但比传入的请求要少。

因此,我正在寻找一种方法,让只有一个长寿命的后台线程与asp.net webapi一起运行,它可以捕获发送给Redis的命令,并通过流水线执行这些命令。

我在考虑通过实现IHostedService接口来运行后台任务,但在这种情况下,后台任务似乎不会与我当前的http请求共享任何状态。因此,对于预定的后台任务来说,实现IhostedService是很方便的,但在我的情况下不是这样,或者我不知道如何实现.

EN

回答 1

Stack Overflow用户

发布于 2021-12-21 00:53:34

基于StackExchange.Redis文档,您可以使用CommandFlags.FireAndForget标志:

代码语言:javascript
代码运行次数:0
运行
复制
[HttpPost]
[Route("api/values/testpostfireforget")]
public  ApiResult<int> DeleteFromBasketId([FromBody] int basketId)
{

    var response = new DeleteFromBasketResponse<int>();
    var cpt = Interlocked.Increment(ref counter);

    db.StringSet($"BASKET_TO_DELETE_{cpt}", cpt.ToString(), flags: CommandFlags.FireAndForget);  

    return response;
}

编辑1:基于注释的另一种解决方案

您可以使用pub/sub方法。像这样的事情应该有效:

代码语言:javascript
代码运行次数:0
运行
复制
public class MessageBatcher
{
    private readonly IDatabase target;
    private readonly BlockingCollection<Action<IDatabaseAsync>> tasks = new();
    private Task worker;

    public MessageBatcher(IDatabase target) => this.target = target;

    public void AddMessage(Action<IDatabaseAsync> task) => tasks.Add(task);
    
    public IDisposable Start(int batchSize)
    {
        var cancellationTokenSource = new CancellationTokenSource();
        worker = Task.Factory.StartNew(state =>
        {
            var count = 0;
            var tokenSource = (CancellationTokenSource) state;
            var box = new StrongBox<IBatch>(target.CreateBatch());
            tokenSource.Token.Register(b => ((StrongBox<IBatch>)b).Value.Execute(), box);
            foreach (var task in tasks.GetConsumingEnumerable(tokenSource.Token))
            {
                var batch = box.Value;
                task(batch);
                if (++count == batchSize)
                {
                    batch.Execute();
                    box.Value = target.CreateBatch();
                    count = 0;
                }
            }
            
        }, cancellationTokenSource, cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);

        return new Disposer(worker, cancellationTokenSource);
    }

    private class Disposer : IDisposable
    {
        private readonly Task worker;
        private readonly CancellationTokenSource tokenSource;

        public Disposer(Task worker, CancellationTokenSource tokenSource) => (this.worker, this.tokenSource) = (worker, tokenSource);

        public void Dispose()
        {
            tokenSource.Cancel();
            worker.Wait();
            tokenSource.Dispose();
        }
    }
}

用法:

代码语言:javascript
代码运行次数:0
运行
复制
private readonly MessageBatcher batcher;

ctor(MessageBatcher  batcher) // ensure that passed `handler` is singleton and already already started
{
    this.batcher= batcher;
}

[HttpPost]
[Route("api/values/testpostfireforget")]
public  ApiResult<int> DeleteFromBasketId([FromBody] int basketId)
{
    var response = new DeleteFromBasketResponse<int>();
    var cpt = Interlocked.Increment(ref counter);

    batcher.AddMessage(db => db.StringSetAsync($"BASKET_TO_DELETE_{cpt}", cpt.ToString(), flags: CommandFlags.FireAndForget));  

    return response;
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70428482

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档