ASP网络应用程序中的后台任务

6
我对C#还比较陌生,最近使用.NET 4.0构建了一个小型Web应用程序。该应用程序有两个部分:一个被设计为永久运行,并将持续从指定的网络资源获取数据;另一个在请求时访问该数据以进行分析。我正在努力解决第一个部分。
我的初始方法是设置一个计时器对象,每5分钟执行一次获取操作(无论这个操作是什么,在这里并不重要)。我会在Application_Start上定义这个计时器,并让它继续存在。
然而,我最近意识到应用程序是基于用户请求创建/销毁的(据我观察,它们似乎会在一段时间的不活动后被销毁)。因此,我的后台活动将停止/恢复,超出了我想要连续运行、没有任何中断的控制。
那么,我的问题来了:这在Web应用程序中可行吗?或者我绝对需要一个单独的Windows服务来处理这种情况?
感谢您的宝贵帮助!
Guillaume

4
我认为使用服务是运行后台任务最“适当”的方式,并确保它按您期望的方式运行。如果您能接受缺点,通过在IIS进程中运行程序可以实现相似的效果。更多细节请参考这里http://stackoverflow.com/questions/1607178/background-task-with-an-asp-net-web-application和这里http://blog.stackoverflow.com/2008/07/easy-background-tasks-in-aspnet/。 - bronsoja
第二个链接正是我在寻找的!我很惊讶那篇codeproject文章和Jeff的原始博客帖子从未出现在我的谷歌搜索结果中。天知道我在发布这篇文章之前搜索了很长时间...非常感谢! - guidupuy
2
做了这两种方式后,我发现为计划任务单独创建一个应用程序比在ASP.NET应用程序中生成线程更可靠和简单。 - weir
1
也许可以尝试类似Quartz.NET的东西 - http://quartznet.sourceforge.net/ - Mike Christensen
7个回答

2

虽然在Web应用程序中这样做并不理想,但它是可行的,只要网站一直在线。

以下是一个示例:我正在全局.asax中创建一个带有过期时间的缓存项。当它过期时,将触发一个事件。您可以在OnRemove()事件中获取您的数据或其他内容。

然后,您可以设置一个调用页面(最好是非常小的页面),该页面将在Application_BeginRequest中触发代码,以添加具有过期时间的缓存项。

global.asax:

private const string VendorNotificationCacheKey = "VendorNotification";
private const int IntervalInMinutes = 60; //Expires after X minutes & runs tasks

protected void Application_Start(object sender, EventArgs e)
{

   //Set value in cache with expiration time
   CacheItemRemovedCallback callback = OnRemove;

   Context.Cache.Add(VendorNotificationCacheKey, DateTime.Now, null, DateTime.Now.AddMinutes(IntervalInMinutes), TimeSpan.Zero,
                    CacheItemPriority.Normal, callback);
}

private void OnRemove(string key, object value, CacheItemRemovedReason reason)
{
    SendVendorNotification();

    //Need Access to HTTPContext so cache can be re-added, so let's call a page. Application_BeginRequest will re-add the cache.
    var siteUrl = ConfigurationManager.AppSettings.Get("SiteUrl");
    var client = new WebClient();
    client.DownloadData(siteUrl + "default.aspx");
    client.Dispose();

}

private void SendVendorNotification()
{
    //Do Tasks here
}

protected void Application_BeginRequest(object sender, EventArgs e)
{
    //Re-add if it doesn't exist
    if (HttpContext.Current.Request.Url.ToString().ToLower().Contains("default.aspx") &&
        HttpContext.Current.Cache[VendorNotificationCacheKey] == null)
    {
        //ReAdd
        CacheItemRemovedCallback callback = OnRemove;
        Context.Cache.Add(VendorNotificationCacheKey, DateTime.Now, null, DateTime.Now.AddMinutes(IntervalInMinutes), TimeSpan.Zero,
                          CacheItemPriority.Normal, callback);
    }
}

如果你的定时任务很快,这个方法会很有效。但如果它是一个长时间运行的过程...那么你一定要把它从你的Web应用程序中拿出来。

只要第一个请求启动了应用程序......即使没有访问者在网站上,这个方法也会每60分钟触发一次。


2
我建议将其放置在Windows服务中。你可以避免上述所有麻烦,其中最大的是IIS重新启动。Windows服务还具有以下好处:
  1. 服务器启动时可以自动启动。如果您在IIS中运行并且服务器重新启动,则必须等待请求以启动您的进程。
  2. 如果需要,可以将此数据获取过程放置在另一台机器上
  3. 如果最终在多个服务器上负载均衡您的网站,则可能会意外地导致多个数据获取过程引起问题
  4. 易于单独维护代码(单一职责原则)。如果代码只做它需要做的事情而不试图愚弄IIS,那么更容易维护代码。

1
创建一个静态类并添加构造函数,以创建一个计时器事件。 但是正如Steve Sloka所提到的那样,IIS有一个超时时间,您需要进行操作以保持站点运行。
using System.Runtime.Remoting.Messaging;

public static class Variables
{
static Variables()
{
    m_wClass = new WorkerClass();

    // creates and registers an event timer
    m_flushTimer = new System.Timers.Timer(1000);
    m_flushTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnFlushTimer);
    m_flushTimer.Start();
}

private static void OnFlushTimer(object o, System.Timers.ElapsedEventArgs args)
{
    // determine the frequency of your update
    if (System.DateTime.Now - m_timer1LastUpdateTime > new System.TimeSpan(0,1,0))
    {
        // call your class to do the update
        m_wClass.DoMyThing();
        m_timer1LastUpdateTime = System.DateTime.Now;
    }
}

private static readonly System.Timers.Timer m_flushTimer;
private static System.DateTime m_timer1LastUpdateTime = System.DateTime.MinValue;
private static readonly WorkerClass m_wClass;
}

public class WorkerClass
{
public delegate WorkerClass MyDelegate();

public void DoMyThing()
{
    m_test = "Hi";
    m_test2 = "Bye";
    //create async call to do the work
    MyDelegate myDel = new MyDelegate(Execute);
    AsyncCallback cb = new AsyncCallback(CommandCallBack);
    IAsyncResult ar = myDel.BeginInvoke(cb, null);
}

private WorkerClass Execute()
{
    //do my stuff in an async call
    m_test2 = "Later";
    return this;
}

public void CommandCallBack(IAsyncResult ar)
{
    // this is called when your task is complete
    AsyncResult asyncResult = (AsyncResult)ar;
    MyDelegate myDel = (MyDelegate)asyncResult.AsyncDelegate;
    WorkerClass command = myDel.EndInvoke(ar);

    // command is a reference to the original class that envoked the async call
    // m_test will equal "Hi"
    // m_test2 will equal "Later";
}

private string m_test;
private string m_test2;
}

0

我认为你可以通过使用BackgroundWorker来实现它,但我更倾向于建议你选择一个服务。


0

应用程序初始化模块适用于 IIS 7.5,可精确执行此类初始化工作。有关该模块的更多详细信息,请单击此处 应用程序初始化模块


0

你的应用程序上下文与 IIS 中的 Worker Process 一起运行。在 IIS 中,有一些默认超时时间,用于确定工作进程何时会被回收(例如,空闲分钟数(20),或定期间隔(1740))。

也就是说,如果你调整了 IIS 中的这些设置,你应该能够让请求保持活动状态,但使用服务的其他答案同样有效,只是实现方式不同。


0

我最近为上传Access文件到数据库创建了一个文件上传功能(不是最好的方法,但只是长期问题的临时解决方案)。 我通过创建一个后台线程来解决它,该线程通过ProcessAccess函数运行,并在完成后被删除。

除非IIS有一个设置,在此设置中,无论是否活动,它都会在一定时间后杀死线程,否则您应该能够创建调用永远不会结束的函数的线程。不要使用递归,因为打开的函数数量最终会爆炸,而只需使用for(;;)循环5000000次即可使其保持忙碌 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接