首页  ·  知识 ·  云计算
ASP.net:实现站点的后台定时任务
shaily  CSDN  综合  编辑:dezai   图片来源:网络
WEB站点本身就后台服务,为什么还要定制后台定时任务呢?其实很简单,做过实际WEB应用的人都知道,一个实际站点内经常要做一些后台处理,如统计、评价、数据更新等操作,其中一些任务是非常有规律
WEB站点本身就后台服务,为什么还要定制后台定时任务呢?其实很简单,做过实际WEB应用的人都知道,一个实际站点内经常要做一些后台处理,如统计、评价、数据更新等操作,其中一些任务是非常有规律地重复,当然可以由管理员定期去执行我们开发好的WEB页上的Click事件(大家知道,IIS是客户端响应才执行后台程序的)。 
 
  另外还一种情况,就是有些Click执行的操作,IIS响应时间是比较长的,比如作者去年做的一个应用,是评价某一地区的XX问题,由于后台执行一个模型系统,Click后要等3分钟左右才出结果(哈哈,这样的等待你受得了吗?)。这时候就可以用后台定时任务来提前实现,将结果存放好或分解成响应快的任务。 
 
  实现原理 
   首先想到的是定时器,在自己开发之前,按照以前的习惯上网先查前人的成果,没想到,还真有人想过了,也做过了。在这就不再分析了,以下是摘录原文: 
   ASP.net 的后台处理技术 
  在ASP.net的WEB开发中经常会碰到这样的一个问题:即用户操作响应慢的情况。 
   出现这种情况的原因可能是本身用户操作就是一个耗时的操作,一般.Net程序设计时均在用户提交操作时即在后台处理,等到处理完成后再将操作结果返回给用户。 在小一点的系统中这样设计简单易行,而且性能上也不会有多大的问题,但在大一点的系统中这种设计就会给用户带来不好的操作体验,影响用户对系统的印象好坏。 
  在以前实施的系统中对这种情况一般有两种处理方式:
  一、将用户操作直接记录到后台数据库,由后台程序定时扫描来执行。
  二、采用Asp.net的定时处理方式,直接在WEB服务器层来进行处理。 
  两种方式差别并不大,第一种方式主要需要一个后台程序来完成这个扫描工作。 
  我在这里简单介绍下第二种方式。 
  它的核心处理就是System.Threading.Timer。这个定时类可以用于在后台定时执行用户提交操作, 
  它的使用方法: 
  System.Threading.TimerCallback t=new System.Threading.TimerCallback (你的处理方法); 
  System.Threading.Timer t = new System.Threading.Timer(t,null,1000,5000); 
  这一段说明是在启动1秒后每隔5秒就调用所指定的代理。 
  在具体实现时我定义了三个类。 
  1、BkExecItem用于保存用户提交操作,同时它也可以序列化到磁盘上,以免关键后台任务丢失。 
  2、BkExec用于执行。它通过反射来调用BkExecItem中所指定的方法。另外它中间还维护一个先入 
  先出队列Queue,这个队列记录全部的后台处理项。 
  3、BkManager完成定时器的初始化,模块的参数配置等功能。 
  
   一个实用ASP.Net 后台处理类 
 
   接下来讨论下ASP.net 的后台处理 ,并会把我们当前项目中应用的一个后台处理类的代码贴上来参考. 
  后台处理也是现在管理系统设计中需要考虑到的一个问题. 
  什么是后台处理,可以简单认为不是在用户进程处理中完成用户提交的操作,而是将这一处理放到服务端后台进程来处理. 
  加入后台处理后,可以提高前台用户的操作速度,改善用户操作体验. 
  对于一般用户来说他对于一个系统的基本要求就是响应及时,用户很难对一个提交操作后需要等待10秒以后的管理系统产生好感,但在实际系统运行中用户操作是很难在短时间内得到响应,所以这个时候后台处理就可以发挥作用了. 
  我在后面所帖代码中,将需要后台处理的任务均定义成一个ExecItem对象.用户提交操作后,系统将就操作转成一个ExecItem对象加入到BkExecManager(后台处理管理对象)中的一个先入先出的队列中. 
  网站在启动时会自动启动BkExecManager,而BkExecManager则启动一个定时器来定时处理后台任务队列. 
  在处理完成时BkExecManager就队列中移去任务对象,如果操作失败将以邮件方式通知管理员来完成问题处理. 
  代码如下:
   1,后台处理管理对象 
  public class BkExecManager 
  { 
  //定时回调。 
  private static TimerCallback timerDelegate; 
  private static Timer stateTimer; 
  private static BkExecer m_Execer; 
  public static string DataPath; 
  public static string BkManager = "XXXX"; 
  public static int BkBufSize = 100; 
  private static int Interval = 10000; 
  public static BkExecer Execer 
  { 
  get { return m_Execer; } 
  } 
  static BkExecManager() 
  { 
  DataPath = System.AppDomain.CurrentDomain.BaseDirectory + "BkItem/"; 
  if (System.Configuration.ConfigurationManager.AppSettings["Interval"] != null) 
  Interval = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["Interval"]); 
  if (System.Configuration.ConfigurationManager.AppSettings["BkBufSize"] != null) 
  BkBufSize = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["BkBufSize"]); 
  if (System.Configuration.ConfigurationManager.AppSettings["BkManager"] != null) 
  BkManager = System.Configuration.ConfigurationManager.AppSettings["BkManager"]; 
  m_Execer = new BkExecer(); 
  //初始化回调 
  timerDelegate = new TimerCallback(m_Execer.DoBkExec); 
  //初始化定时器 
  stateTimer = new Timer(timerDelegate, null, 5000, Interval); 
  } 
  /**//// 
  /// 停止定时器. 
  /// 
  static void BkExecQuit() 
  { 
  stateTimer.Dispose(); 
  } 
  } 
  2,后台处理执行 
  public class BkExecer 
  { 
  //维护一个前进前出的队列。 
  private Queue m_BkExecItemList; 
  private static object lockHelper = new object(); 
  private static bool m_IsBusy = false; 
  public static bool IsBusy 
  { 
  get { return m_IsBusy; } 
  } 
  public BkExecer() 
  { 
  m_BkExecItemList = new Queue(BkExecManager.BkBufSize); 
  /**/////读入待处理事项 
  InitData(); 
  } 
  private void InitData() 
  { 
  lock (lockHelper) 
  { 
  string[] fnl = Directory.GetFiles(BkExecManager.DataPath); 
  foreach (string s in fnl) 
  { 
  if (!s.Contains(BKExecItemState.出错.ToString())) 
  { 
  ExecItem ei = ExecItem.GetObject(s); 
  m_BkExecItemList.Enqueue(ei); 
  } 
  } 
  } 
  } 
  public void AddBkExecItem(ExecItem ei) 
  { 
  lock (lockHelper) 
  { 
  //锁定资源。 
  m_BkExecItemList.Enqueue(ei); 
  } 
  } 
  public void DoBkExec(object Msg) 
  { 
  ExecItem ei; 
  while (m_BkExecItemList.Count >0) 
  { 
  lock (lockHelper) 
  { 
  ei = m_BkExecItemList.Dequeue(); 
  } 
  int rv = -1; 
  try 
  { 
  BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance | 
  BindingFlags.Public | BindingFlags.Static; 
  object t = ei.ExecItemClass.InvokeMember(ei.ExecItemMethed, flags, 
  null, null, ei.ExecItemParamList); 
  if (t != null) 
  rv = Convert.ToInt32(t); 
  else 
  rv = 0; //如果是无返回则直接设置零. 
  } 
  catch (Exception e) 
  { 
  //更新Ei的状态,保存到磁盘。 
  ei.FinishBkExec(false, e.Message); 
  } 
  finally 
  { 
  //更新Ei的状态,删除存件 
  //保存到磁盘。 
  if (rv >= 0) 
  ei.FinishBkExec(true, ""); 
  else 
  ei.FinishBkExec(false, rv.ToString()); 
  } 
  } 
  } 
  } 
  3,任务对象 
  public enum BKExecItemState { 待执行, 完成, 出错 }; 
  [Serializable] 
  /**//// 
  /// 后台命令集合 
  /// 直接将这些后台命令二进制序列化到WEb服务器上保存。 
  /// 如果完成后则从Web服务器上删除。 
  /// 如果异常则发邮件通知管理员。 
  /// 
  public class ExecItem 
  { 
  /**//// 
  /// 磁盘文档名称 。 
  /// 
  private string BKStoreFileName = ""; 
  private string ErrMsg = ""; 
  private BKExecItemState m_ItemState; 
  public BKExecItemState ItemState 
  { 
  get { return m_ItemState; } 
  } 
  private DateTime m_ExecItemExecTime; 
  public DateTime ExecItemExecTime 
  { 
  get { return m_ExecItemExecTime; } 
  } 
  private DateTime m_ExecItemCreateTime; 
  public DateTime ExecItemCreateTime 
  { 
  get { return m_ExecItemCreateTime; } 
  } 
  private string m_ExecItemName; 
  public string ExecItemName 
  { 
  get { return m_ExecItemName; } 
  } 
  private Type m_ExecItemClass; 
  public Type ExecItemClass 
  { 
  get { return m_ExecItemClass; } 
  } 
  private string m_ExecItemMethed; 
  public string ExecItemMethed 
  { 
  get { return m_ExecItemMethed; } 
  } 
  private object[] m_ExecItemParamList; 
  public object[] ExecItemParamList 
  { 
  get { return m_ExecItemParamList; } 
  } 
  private string m_Op; 
  /**//// 
  /// 后台任务对象 
  /// 
  /// 对象类型 
  /// 调用方法 
  /// 调用参数 
  /// 任务名 
  /// 提交人 
  /// 是否保存到磁盘 
  public ExecItem(Type objtype, string ExecMethod, object[] param, string ExecName, string Op, bool SavetoDisk) 
  { 
  this.BKStoreFileName = String.Format("{0} {1} {2}.bin", 
  DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss"), ExecMethod, Op); 
  this.m_ExecItemClass = objtype; 
  this.m_ExecItemCreateTime = DateTime.Now; 
  this.m_ExecItemExecTime = DateTime.Now; 
  this.m_ExecItemMethed = ExecMethod; 
  this.m_ExecItemName = ExecName; 
  this.m_ExecItemParamList = param; 
  this.m_ItemState = BKExecItemState.待执行; 
  this.m_Op = Op; 
  if (SavetoDisk) 
  SaveToDisk(); 
  } 
  private void SaveToDisk() 
  { 
  IFormatter formatter = new BinaryFormatter(); 
  Stream stream = new FileStream(BkExecManager.DataPath + BKStoreFileName, 
  FileMode.Create, FileAccess.Write, FileShare.None); 
  formatter.Serialize(stream, this); 
  stream.Close(); 
  } 
  private void SaveToDisk2() 
  { 
  // 
  string basedir = System.AppDomain.CurrentDomain.BaseDirectory; 
  this.BKStoreFileName = String.Format("{0} {1} {2} {3}.bin", 
  m_ExecItemCreateTime.ToString("yyyy-MM-dd HH-mm-ss"), 
  this.m_ExecItemMethed, 
  m_Op, 
  m_ItemState.ToString()); 
  IFormatter formatter = new BinaryFormatter(); 
  Stream stream = new FileStream(BkExecManager.DataPath + BKStoreFileName, 
  FileMode.Create, FileAccess.Write, FileShare.None); 
  formatter.Serialize(stream, this); 
  stream.Close(); 
  } 
  public static ExecItem GetObject(string s) 
  { 
  IFormatter formatter = new BinaryFormatter(); 
  Stream stream = new FileStream(s, FileMode.Open, FileAccess.Read, FileShare.None); 
  ExecItem e = (ExecItem) formatter.Deserialize(stream); 
  stream.Close(); 
  return e; 
  } 
  public void FinishBkExec(bool DoneOk, string Msg) 
  { 
  string FileName = BkExecManager.DataPath + BKStoreFileName; 
  m_ExecItemExecTime = DateTime.Now; 
  if (File.Exists(FileName)) 
  File.Delete(FileName); 
  if (!DoneOk) 
  { 
  m_ItemState = BKExecItemState.出错; 
  ErrMsg = Msg; 
  SaveToDisk2(); 
  MakeMail(); 
  } 
  } 
  private void MakeMail() 
  { 
  StringBuilder sb = new StringBuilder(); 
  sb.Append("提交人:").Append(this.m_Op).Append("
"); 
  sb.Append("提交时间:").Append(this.ExecItemCreateTime).Append("
"); 
  sb.Append("对象:").Append(this.m_ExecItemClass.Name).Append("
"); 
  sb.Append("方法:").Append(this.m_ExecItemMethed).Append("
"); 
  sb.Append("参数:"); 
  foreach (object o in this.m_ExecItemParamList) 
  sb.Append(o.ToString()).Append(","); 
  sb.Append("
"); 
  sb.Append("执行时间:").Append(this.m_ExecItemExecTime).Append("
"); 
  sb.Append("错误信息:").Append(this.ErrMsg).Append("
"); 
  string mb = sb.ToString(); 
  //APP.Mail.Send(m_Op + ":" + m_ExecItemClass.Name + "后台处理错", mb, "", BkExecManager.BkManager, ""); 
  } 
  } 
  具体调用方法为 
  1,首先新调一个后台任务对象. 
  2,将之加入到任务队列中. 
  ExecItem ei = new ExecItem(typeof(CacheManager), 
  "RefreshObject", 
  new object[] { Objtype, Params, ct }, 
  "缓存刷新", 
  "", 
  false); //注意以后可以设置为false,即刷新任务不保存到磁盘,以免影响磁盘性能. 
  BkExecManager.Execer.AddBkExecItem(ei); 
  目前项目中运行良好,后期还需继续完善.其目前不足 :不能让用户知道所提交的操作执行进度和操作结果等.
本文作者:shaily 来源:CSDN
CIO之家 www.ciozj.com 微信公众号:imciow
   
免责声明:本站转载此文章旨在分享信息,不代表对其内容的完全认同。文章来源已尽可能注明,若涉及版权问题,请及时与我们联系,我们将积极配合处理。同时,我们无法对文章内容的真实性、准确性及完整性进行完全保证,对于因文章内容而产生的任何后果,本账号不承担法律责任。转载仅出于传播目的,读者应自行对内容进行核实与判断。请谨慎参考文章信息,一切责任由读者自行承担。
延伸阅读