【问题标题】:Call Async Method from Session_Start从 Session_Start 调用异步方法
【发布时间】:2016-12-21 17:40:17
【问题描述】:

如何从 Global.asax 中的Session_Start 调用 async 方法?

全球.asax:

    protected async Task Session_Start(object sender, EventArgs e)
    {            
        Session.Timeout = 10;

        // Do some asynch work
        await repository.SetStatsInfo(System.DateTime.Now);            
    }

异步方法:

    public async Task SetStatsInfo(DateTime time)
    {
        using (ApplicationDBContext db = new ApplicationDBContext())
        {
            // Do stuff (update visitors counter in db) ..

            await db.SaveChangesAsync();

        }
    }

我可以同步运行它(定义void Session_Start 等),它正在工作,但更喜欢异步方式,这样访问数据库就不会阻塞。

像这样使用 Session_Start 的“异步任务”运行,代码没有执行, session_start 内的断点未命中。

【问题讨论】:

  • 克里斯说得对。这没有理由是异步的,因为它根本没有意义。 stackoverflow.com/a/38956850/2410379
  • @DavidPine 但是,当我们需要使用只能作为async Task<T> 调用使用的API 时,我们应该怎么做? Stephen Clearly 告诉我们,我们绝不能调用 GetAwaiter().Result,因为存在死锁的风险 - 那么 ASP.NET 应用程序的 Global Application_Start 方法如何安全地调用异步方法?

标签: asp.net-mvc session asynchronous async-await .net-4.5


【解决方案1】:

据我了解,ASP 有一个指定的线程,它是唯一可以访问HttpContext.Current 对象的线程,进而可以访问 Session (HttpContext.Current.Session) 很像 Windows 应用程序中的 UI 线程。因此,在 Session_Start 回调中执行 .Wait().Result 会给您带来未知的结果和/或进程死锁。

似乎有很多方法可以管理任务的执行线程,主要的一种是通过 TaskScheduler 类 https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler(v=vs.110).aspx#Sync 指定您的任务在特定的同步上下文中运行

但是,由于 Task 类旨在实现来自 APM(异步编程模型)模式的 IAsyncResult,这使得 Task 向后兼容旧的 APM 模式代码(据我了解,ASP 最初是基于)。尽管https://blogs.msdn.microsoft.com/mazhou/2011/10/04/the-asynchronous-programming-models/(标准APM)需要一些集成工作。

.Net 4.5 引入了一个不错的任务包装器 (EventHandlerTaskAsyncHelper) 来执行异步操作,使用 APM 样式 HttpApplication 来支持。它满足访问Session对象的所有要求,并在HttpApplication中正确执行:

public override void Init()
{
    base.Init();
    //EventHandlerTaskAsyncHelper Wraps the task call in an APM-style BeginEventHandler, EndEventHandler
    var wrapper = new EventHandlerTaskAsyncHelper(AsyncSessionStart);
    this.AddOnAcquireRequestStateAsync(wrapper.BeginEventHandler, wrapper.EndEventHandler);
}

private async Task AsyncSessionStart(Object sender, EventArgs evtArgs)
{       
    //The only caveat is we have to check IsNewSession to see if it was created in this call
    //This doesn't need to be applied for other AddOn*Async wire-ups
    if (!Session.IsNewSession)
        return;

    await doSomethingAsync();
}

//I recall seeing something that for session state to be active, this callback has to be declared, even if empty
protected void Session_Start(object sender, EventArgs e)
{
    //Synchronous session
}

【讨论】:

  • 仅供参考:具有只读会话的​​页面需要空的 Session_OnStart 方法,因为 SessionStateModule 检查是否定义了 Session_OnStart 同步处理程序以强制保存会话状态,即使在只读会话上也是如此:referencesource.microsoft.com/#System.Web/State/…
  • 对此注意:HttpApplication.Session 属性会在没有会话时抛出异常(例如,在获取捆绑资源时会发生这种情况)。所以你最好用if (HttpContext.Current?.Session?.IsNewSession != true) return;进行检查
【解决方案2】:

像 Global.asax 中的Session_Start 这样的方法很特别。你不能随意定义新的。该框架运行它被编程运行的那个,并且没有提供异步版本。因此,不会运行任何异步版本。

但是,无论如何,异步是没有意义的。 Global.asax 方法在应用程序池的启动和关闭时被调用。因此,在任何时候放弃操作线程都是没有意义的,因为在它完成工作之前不会发生任何其他事情。

我不完全确定您在做什么,但根据您代码中的注释,这听起来无论如何都不是正确的地方。同样,此代码只运行一次,而不是每个请求。如果您希望每个请求都发生某些事情,请查看操作过滤器之类的东西。

【讨论】:

  • 正如您所指出的,目的是在每次新会话开始时更新数据库中的站点访问者计数(运行同步,据我所知)。但是为什么 Session_Start 不是一个合适的地方呢?
  • 因为 Global.asax 仅在应用程序池启动或关闭时运行。与此同时,针对任意数量的不同用户的任意数量的请求都会发生。如果您要向 Session_Start 添加某种访问者跟踪逻辑,它只会在 App Pool 启动时发生一次,并且 App Pool 可能会在数小时、数天甚至数周内不会重新启动,具体取决于它的配置方式和状态服务器的。显然,这不会为您提供非常相关的信息。
  • 你指的是Application_Start吗?每次创建新会话时都会触发Session_Start 事件,如:http://forums.asp.net/t/1230163.aspx?What+s+the+difference+between+Application_Start+and+Session_Start+in+Global+aspx+
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-23
  • 2017-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-13
相关资源
最近更新 更多