从Session_Start调用异步方法

7

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

Global.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等),这是可行的,但我更喜欢异步方式,以便访问数据库时不会阻塞。

使用“async Task”运行Session_Start,代码不会执行,session_start内部的断点也没有被触发。


1
克里斯说得没错。这个函数没有必要是异步的,因为这根本就不合理。https://dev59.com/2Znga4cB1Zd3GeqPetsP#38956850 - David Pine
2
@DavidPine 但是当我们需要调用一个仅作为async Task<T>调用可用的API时,我们该怎么办呢?Stephen Clearly已经告诉我们,由于死锁的风险,我们绝不能调用GetAwaiter().Result - 那么ASP.NET应用程序的Global Application_Start方法如何安全地调用异步方法呢? - Dai
Session_Start 是一个事件处理程序。能够异步处理事件显然是有意义的。 - Johann
2个回答

12
根据我的理解,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类的设计是为了实现IAsyncResult接口,该接口来自APM(异步编程模型)模式,因此这使得Task向后兼容旧的APM模式代码(据我所知,ASP最初就是在其上构建的)。虽然需要一些集成工作https://blogs.msdn.microsoft.com/mazhou/2011/10/04/the-asynchronous-programming-models/(标准APM)。
.Net 4.5引入了一个很好的Task包装器(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
}

1
请注意:对于具有只读会话的页面,空的Session_OnStart方法是必需的,因为SessionStateModule检查是否定义了Session_OnStart同步处理程序,以强制保存会话状态,即使在只读会话上也是如此:http://referencesource.microsoft.com/#System.Web/State/SessionStateModule.cs,1292 - Nitin Agarwal
2
关于这个注意事项:如果没有会话,HttpApplication.Session 属性会抛出异常(例如在获取捆绑资源时)。因此最好使用 if (HttpContext.Current?.Session?.IsNewSession != true) return; 进行检查。 - Gabriel Luci
我刚刚实施了这个技术,用于在Global.asax.cs中实现对gitlab服务的异步调用以检索版本。我想确认关于必须有一个Session_Start方法的猜测 - 我已经有一个但它仍然不起作用,但我注意到我的方法是无参数的,因为我没有使用sender或EventArgs。我在签名中添加了它们,然后一切都正常工作了。我还必须在HttpContext.Current?.Session上包含空值检查 - 似乎该方法在加载时被调用三到四次,只有一次使用了实例化的Session。 - Milton

3
那些像Global.asax中的Session_Start这样的方法是特殊的。你不能随意定义新的方法。框架运行它编程运行的方法,而且没有提供任何异步版本。因此,永远不会运行任何异步版本。
然而,即便是异步也没有什么意义。Global.asax方法在应用程序池启动和关闭时被调用。因此,在任何时候放弃操作线程都没有意义,因为在它完成工作之前不会发生任何其他事情。
我不完全确定你在做什么,但根据你代码中的注释,似乎这并不是正确的地方。同样,这段代码只运行一次,而不是每个请求。如果你想要每个请求发生某些操作,请考虑使用类似于“Action Filter”的东西。

正如您所指出的那样,意图是在每次启动新会话时(运行同步,我现在理解了)更新数据库中的站点访问者计数。但为什么Session_Start不是一个合适的地方呢? - Danielle
因为Global.asax只在应用程序池启动或关闭时运行。同时,任意数量的请求,针对任意数量的不同用户发生。如果您要在Session_Start中添加某种访问者跟踪逻辑,则仅会在应用程序池启动时发生一次,并且应用程序池可能根据其配置和服务器状态而不会重新启动数小时、数天甚至数周。显然,这不会给您提供非常相关的信息。 - Chris Pratt
3
你是在指 Application_Start 吗?正如在 [http://forums.asp.net/t/1230163.aspx?What+s+the+difference+between+Application_Start+and+Session_Start+in+Global+aspx+] 中所提到的,Session_Start 事件会在每次创建新会话时触发。 - Danielle

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