最近遇到了这样的场景:每隔一段时间,需要在后台使用队列对一批数据进行业务处理。

Quartz.NET是一种选择,在 .NET Core中,可以使用IHostedService执行后台定时任务。在本篇中,首先尝试把队列还原到最简单、原始的状态,然后给出以上场景问题的具体解决方案。

假设一个队列有8个元素。现在abcd依次进入队列。

0 1 2 3 4 5 6 7
a b c d
head tail

ab依次出队列。

0 1 2 3 4 5 6 7
c d
head tail

可以想象,随着不断地入列出列,head和tail的位置不断往后,当tail在7号位的时候,虽然队列里还有空间,但此时数据就无法入队列了。

如何才可以继续入队列呢

首先想到的是数据搬移。当数据无法进入队列,首先让队列项出列,进入到另外一个新队列,这个新队列就可以再次接收数据入队列了。但是,搬移整个队列中的数据的时间复杂度为O(n),而原先出队列的时间复杂度是O(1),这种方式不够理想。

还有一种思路是使用循环队列。当tail指向最后一个位置,此时有新的数据进入队列,tail就来到头部指向0号位置,这样这个队列就可以循环使用了。

0 1 2 3 4 5 6 7
h i j
head tail

现在a入栈。

0 1 2 3 4 5 6 7
h i j a
tail head

队列有很多种实现

比如在生产消费模型中可以用阻塞队列。当生产队列为空的时候,为了不让消费者取数据,生产队列的Dequeue行为会被阻塞;而当生产队列满的时候,为了不让更多的数据进来,生产队列的Enqueue行为被阻塞。

线程安全的队列叫并发队列,如C#中的ConcurrentQueue

线程池内部也使用了队列机制。因为CPU的资源是有限的,过多的线程会导致CPU频繁地在线程之间切换。线程池内通过维护一定数量的线程来减轻CPU的负担。当线程池没有多余的线程可供使用,请求过来,一种方式是拒绝请求,另外一种方式是让请求队列阻塞,等到线程池内有线程可供使用,请求队列出列执行。用链表实现的队列是无界队列(unbounded queue),这种做法可能会导致过多的请求在排队,等待响应时间过长。用数组实现的队列是有界队列(bounded queue),当线程池已满,请求过来就会被拒绝。对有界队列来说数组的大小设置很讲究。

来模拟一个数组队列。

public class ArrayQueue
{
private string[] items;
private int n = 0; //数组长度
private int head = 0;
private int tail = 0; public ArrayQueue(int capacity)
{
n = capacity;
items = new string[capacity];
} public bool Enqueue(string item)
{
if(tail==n){
return false;
}
items[tail] = item;
++tail;
return true;
} public string Dequeue()
{
if(head==null){
return null;
}
string ret = items[head];
++head;
return ret;
}
}

以上就是一个最简单的、用数组实现的队列。

再次回到要解决的场景问题。解决思路大致是:实现IHostedService接口,在其中执行定时任务,每次把队列项放到队列中,并定义出队列的方法,在其中执行业务逻辑。

关于队列,通过以下的步骤使其在后台运行。

  • 队列项(MessageQueueItem):具备唯一标识、委托、添加到队列中的时间等属性
  • 队列(MessageQueue):维护着Dictionary<string, MessageQueueItem>静态字典集合
  • MessageQueueUtility类:决定着如何运行,比如队列执行的间隔时间、垃圾回收
  • MessageQueueThreadUtility类:维护队列线程,提供队列在后台运行的方法
  • Startup.cs中的Configure中调用MessageQueueThreadUtility中的方法使队列在后台运行

队列项(MessageQueueItem)

public class MessageQueueItem
{
public MessageQueueItem(string key, Action action, string description=null)
{
Key = key;
Action = action;
Description = description;
AddTime = DateTime.Now;
} public string Key{get;set;}
public Actioin Action{get;set;}
public DateTime AddTime{get;set;}
public string Description{get;set;}
}

队列(MessageQueue),维护着针对队列项的一个静态字典集合。

public class MessageQueue
{
public static Dictionary<string, MessageQueueItem> MessageQueueDictionary = new Dictionary<string, MessageQueueItem>(StringComparer.OrdinalIgnoreCase); public static object MessageQueueSyncLock = new object();
public static object OperateLock = new object(); public static void OperateQueue()
{
lock(OperateLock)
{
var mq = new MessageQueue();
var key = mq.GetCurrentKey();
while(!string.IsNullOrEmpty(key))
{
var mqItem = mq.GetItem(key);
mqItem.Action();
mq.Remove(key);
key = mq.GetCurrentKey();
}
}
} public string GetCurrentKey()
{
lock(MessageQueueSyncLock)
{
return MessageQueueDictionary.Keys.FirstOrDefault();
}
} public MessageQueueItem GetItem(string key)
{
lock(MessageQueueSyncLock)
{
if(MessageQueueDictionary.ContainsKey(key))
{
return MessageQueueDictionary[key];
}
return null;
}
} public void Remove(string key)
{
lock(MessageQueueSyncLock)
{
if(MessageQueueDictionary.ContainsKey(key))
{
MessageQueueDictionary.Remove(key);
}
}
} public MessageQueueItem Add(string key, Action actioin)
{
lock(MessageQueueSyncLock)
{
var mqItem = new MessageQueueItem(key, action);
MessageQueueDictionary[key] = mqItem;
return mqItem;
}
} public int GetCount()
{
lock(MessageQueueSyncLock)
{
return MessageQueueDictionary.Count;
}
}
}

MessageQueueUtility类, 决定着队列运行的节奏。

public class MessageQueueUtility
{
private readonly int _sleepMilliSeconds;
public MessageQueueUtility(int sleepMilliSeconds=1000)
{
_sleepMilliSeconds = sleepMilliSeoncds;
} ~MessageQueueUtility()
{
MessageQueue.OperateQueue();
} public void Run
{
do
{
MessageQueue.OperateQueue();
Thread.Sleep(_sleepMilliSeconds);
} while(true)
}
}

MessageQueueThreadUtility类,管理队列的线程,并让其在后台运行。

public static class MessageQueueThreadUtility
{
public static Dictionary<string, Thread> AsyncThreadCollection = new Dictioanry<string, Thread>();
public static void Register(string threadUniqueName)
{
{
MessageQueueUtility messageQueueUtility = new MessageQueueUtility();
Thread messageQueueThread = new Thread(messageQueueUtility.Run){
Name = threadUniqueName
};
AsyncThreadCollection.Add(messageQueueThread.Name, messageQueueThread);
} AsyncThreadCollection.Values.ToList().ForEach(z => {
z.IsBackground = true;
z.Start();
});
}
}

Startup.cs中注册。

public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
} public void Configure(IApplicationBuilder app, IHostingEnvironment env...)
{
RegisterMessageQueueThreads();
} private void RegisterMessageQueueThreads()
{
MessageQueueThreadUtility.Register("");
}
}

最后在IHostedService的实现类中把队列项丢给队列。

public class MyBackgroundSerivce : IHostedService, IDisposable
{
private Timer _timer;
public IServiceProvider Services{get;} public MyBackgroundService(IServiceProvider services)
{
Serivces = services;
} public void Dispose()
{
_timer?.Dispose();
} public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
return Task.CompletedTask;
} public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite,0);
return Task.CompletedTask;
} private void DoWork(object state)
{
using(var scope = Services.CreateScope())
{
using(var db = scope.ServiceProvider.GetRequiredService<MyDbContext>())
{
...
var mq = new MessageQueue();
mq.Add("somekey", DealQueueItem);
}
}
} private void DealQueueItem()
{
var mq = new MessageQueue();
var key = mq.GetCurrentKey();
var item = mq.GetItem(key);
if(item!=null)
{
using(var scope = Services.CreateScope())
{
using(var db = scope.ServiceProvider.GetRequiredService<MyDbContext>())
{
//执行业务逻辑
}
}
}
}
}

当需要使用上下文的时候,首先通过IServiceProviderCreateScope方法得到ISerivceScope,再通过它的ServiceProvider属性获取依赖倒置容器中的上下文服务。

以上,用IHostedService结合队列解决了开篇提到的场景问题,如果您有很好的想法,我们一起交流吧。文中的队列部分来自"盛派网络"的Senparc.Weixin SDK源码。

.NET Core中使用IHostedService结合队列执行定时任务的更多相关文章

  1. java中服务器启动时,执行定时任务

    package com.ripsoft.util; import java.util.Calendar; import java.util.Timer; import javax.servlet.Se ...

  2. .NET Core 中基于 IHostedService 实现后台定时任务

    .NET Core 2.0 引入了 IHostedService ,基于它可以很方便地执行后台任务,.NET Core 2.1 则锦上添花地提供了 IHostedService 的默认实现基类 Bac ...

  3. WSL2+Docker部署RabbitMQ以及在Asp.net core 中使用RabbitMQ示例(1)

    本文主要在于最近因疫情不能外出,在家研究的一些技术积累. 主要用到的技术以及知识点: WSL 2 WSL 2+Docker Docker+RabbitMQ 在ASP.NET Core中使用Rabbit ...

  4. .NET Core中使用RabbitMQ正确方式

    .NET Core中使用RabbitMQ正确方式 首先甩官网:http://www.rabbitmq.com/ 然后是.NET Client链接:http://www.rabbitmq.com/dot ...

  5. 在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度

    在这篇文章中,我将介绍如何使用ASP.NET Core托管服务运行Quartz.NET作业.这样的好处是我们可以在应用程序启动和停止时很方便的来控制我们的Job的运行状态.接下来我将演示如何创建一个简 ...

  6. 如何在Spring Boot 中动态设定与执行定时任务

    本篇文章的目的是记录并实现在Spring Boot中,动态设定与执行定时任务. 我的开发项目是 Maven 项目,所以首先需要在 pom.xml 文件中加入相关的依赖.依赖代码如下所示: <de ...

  7. 『学了就忘』Linux系统定时任务 — 88、循环执行定时任务

    目录 1.crond服务管理与访问控制 2.crontab命令的访问控制 3.用户级别的crontab命令 4.crontab命令的注意事项 5.系统的crontab设置 (1)/etc/cronta ...

  8. 使用Redis Stream来做消息队列和在Asp.Net Core中的实现

    写在前面 我一直以来使用redis的时候,很多低烈度需求(并发要求不是很高)需要用到消息队列的时候,在项目本身已经使用了Redis的情况下都想直接用Redis来做消息队列,而不想引入新的服务,kafk ...

  9. 在 ASP.NET Core 中执行租户服务

    在 ASP.NET Core 中执行租户服务 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译自: http://gunna ...

随机推荐

  1. [转]编程珠玑第五章二分搜索(折半查找)之java实现

    http://blog.csdn.net/hwe_xc/article/details/51813080 二分搜索又称为折半查找,用来高效快速的解决如下问题: 我们需要确定排序后的数组x[0..n-1 ...

  2. centos 6 切换base源

    切换为阿里云源: mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup && wg ...

  3. 【C#】使用OWIN创建Web API

    OWIN的介绍 OWIN 的全称是 "Open Web Interface for .NET", OWIN 在 .NET Web 服务器和 .NET Web 应用之间定义了一套标准 ...

  4. Flink--time-window 的高级用法

    1.现实世界中的时间是不一致的,在 flink 中被划分为事件时间,提取时间,处理时间三种. 2.如果以 EventTime 为基准来定义时间窗口那将形成 EventTimeWindow,要求消息本身 ...

  5. Codeforces 875F Royal Questions (看题解)

    我还以为是什么板子题呢... 我们把儿子当做点, 公主当做边, 然后就是求边权值最大基环树森林. #include<bits/stdc++.h> #define LL long long ...

  6. oracle的DBMS_JOB相关知识

    http://langgufu.iteye.com/blog/1179235 查看当前定时任务 select job,next_date,next_sec,failures,broken from u ...

  7. ContentProvider工作原理

    --摘自<android插件化开发指南> 1.系统管理类app,比如手机助手,有机会频繁使用ContentProvider 2.通讯录或者短信数据,是以ContentProvider的形式 ...

  8. poj 3067 Japan 【树状数组】

    <题目链接> 题目大意: 有两个点集,这两个点集从上至下分别从1~n,1~m编号,现在给出n组数据,(x,y),表示左边点集编号为x的点与右边点集编号为y的点相连,现在要求计算这些线段的交 ...

  9. POJ 1459 - Power Network 【Ek-最大流】

    <题目链接> 题目大意:给出 n 个点,其中包括 np个发电站,nc 个消费者, 剩下的全部都是中转点,再给出 这些点中的m 条边,代表这两点间的最大传输电量,并且给出发电站的最大发送电量 ...

  10. datatables后端分页

    0x01 缘由 平时较少涉及前端,这次本以为模板中有表单,分页跳转搜索功能都比较齐全,可以高枕无忧,但是细看模板中的分页跳转是不需要与后台交互的,数据一次性写在前端,再有前端插件完成分页. 这种方式肯 ...