如何从不同的Thread访问MVC.Net 4中的Session

时间:2014-04-15 07:17:25

标签: session asynchronous task httpcontext

我有一个MVC.Net4应用程序,其中我有Longrunning背景操作,这就是我使用System.Threading.Tasks.Task类的原因。

我在用户点击GUI上的某个按钮后启动任务,从该任务我将使用我需要等待的实习API的异步方法。这一切都有效。

public ActionResult DoAsyncAction()
        {
            //ReturnValue that needs to be further populated by the async action in productive environment
            var arv = new AsyncReturnValue
            {
                ProgressBar = new ProgressBar {Action = "SomeAction", User = "SomeUser"}
            };
            var t = new Task<AsyncReturnValue>(DoAction, arv);

            //Add a Progressbar before Task starts so i can visualize the process on the view
            HttpContext.GetSession().ProgressBars.Add(arv.ProgressBar);

            //from my understanding this is similar to an event that gets triggered when my DoAction Method finished so i need to remove 
            //the progressbar there again since the process will be finished in that case
            t.ContinueWith(DoActionkComplete);
            t.Start();

            //Returns the User to the Index Page while the Task is processing
            return View("Index");
}

现在我真正想做的是可视化操作。我在GUI上使用jQuery Progressbars,在会话中使用我自己的ProgressBar对象。我在Session上有一个ProgressBars列表,并且在这些ProgressBars的列表中有一个强类型的PartialView。

ProgressBar类:

public class ProgressBar
    {
        public string Action { get; set; }
        public string User { get; set; }
    }

PartialView:

@using AsyncProj.Models
@model List<AsyncProj.Models.ProgressBar>

@{
    ViewBag.Title = "ProgressPartial";
}
    @{
        var foo = (MySessionObject) HttpContext.Current.Session["__MySessionObject"];
        foreach (var pb in foo.ProgressBars)
         {
            <div style="border: 1px solid black">
                    <p>@pb.Action</p>
                    <div id="progressbar">This will be turned into a ProgressBar via jQuery.</div >
            </div>
         }
    }

然后是我在会话中的对象:

     public class MySessionObject
{
    public List<ProgressBar> ProgressBars { get; set; }
    public string User { get; set; }}

每当我开始一个新任务时,我都会在该列表中添加另一个ProgressBar,它可以正常工作。

当我想再次从Session中删除ProgressBars时,我无法进入Troubles。

在我在Task.ContinueWith()中设置的DoActionkComplete方法中,我想删除与完成的操作相对应的ProgressBar。我在那里有ProgressBar Ready,它存储在我的AsyncReturnValue类中,此时我在Task.Result中有:

 public class AsyncReturnValue
    {
        public ProgressBar ProgressBar { get; set; }
    }

在此方法中,我想从Session中删除Progressbar HttpContext.GetSession().ProgressBars.Remove(pbToRemove)。但是我的问题仍然在不同的线程上运行,所以我没有有效的HttpContext,我的SessionObject在该线程上为空。

这就是我的DoActionComplete方法现在所看到的:

public void DoActionkComplete(Task<AsyncReturnValue> t)
        {
            //i set the user hardcode because its only a demo 
            DeleteProgress.Add(new ProgressBarDeleteObject {ProgressBar = t.Result.ProgressBar, User = "Asd123"});
        }

我创建了一个解决方法,我的控制器上有一个静态的Progressbars列表。在DoActionComplete方法中,我将要删除的ProgressBars添加到该列表中。我需要使用轮询(使用jQuery $ .get()和setinterval)来删除它们。

我有一个DeleteList的自定义类,我可以在其上设置一个用户名,所以我知道谁是该ProgressBar的所有者并且只向他显示,否则每个人都会看到它,因为它的静态。

 public class ProgressBarDeleteObject
    {
        public ProgressBar ProgressBar { get; set; }
        public string User { get; set; }
    }

别误会我的意思,我的解决方法很好但我想知道干净的方式。据我所知,控制器上的静态列表在技术上可能会变得非常大并且减慢了网站的速度。当ApplicationPool重新启动应用程序时,此类列表也会丢失其条目。

所以我的实际问题是如何从不同的线程访问HttpContext SessionObject,就像我使用?如果它不可能,那么实现我想要的正确方法是什么?

2 个答案:

答案 0 :(得分:1)

  

所以我的实际问题是如何从不同的线程访问HttpContext SessionObject,比如我使用?

那是不可能的。

  

如果不可能,那么实现我想要的东西是什么?

首先,让我们回到最初的场景。问题出在这里:

  

我有一个MVC.Net4应用程序,其中我有Longrunning背景操作,这就是我使用System.Threading.Tasks.Task类的原因。

对于那种情况,这是错误的解决方案。正确的解决方案是拥有一个可靠的队列(Azure队列/ MSMQ)和一个独立的后台进程(Azure webjob / Win32服务)进行处理。这是一个更可靠的解决方案,因为您在ASP.NET线程池中所做的任何工作都可能丢失(特别是如果您没有通知ASP.NET有关该工作)。

设置此架构后,您就可以使用SignalR从Web服务器与客户进行通信。如果必须,SignalR将使用轮询,但它也可以使用更有效的方法(例如websockets)。

答案 1 :(得分:-1)

您可以指定SynchroniztionContext任务继续的ContinueWith,然后您应该可以访问进度条。尝试将t.ContinueWith(DoActionkComplete);来电更改为

t.ContinueWith(DoActionkComplete, TaskScheduler.FromCurrentSynchronizationContext());

如果您使用的是.NET 4.5,则可以使用async\await

重写您的方法
    public async Task<ActionResult> DoAsyncAction()
    {
        //ReturnValue that needs to be further populated by the async action in productive environment
        var arv = new AsyncReturnValue
        {
            ProgressBar = new ProgressBar {Action = "SomeAction", User = "SomeUser"}
        };

        //Add a Progressbar before Task starts so i can visualize the process on the view
        HttpContext.GetSession().ProgressBars.Add(arv.ProgressBar);

        var result = await Task.Run(DoAction());

        //i set the user hardcode because its only a demo 
        DeleteProgress.Add(new ProgressBarDeleteObject {ProgressBar = result.ProgressBar, User = "Asd123"});

        //Returns the User to the Index Page while the Task is processing
        return View("Index");

}

如果您同时使用DoAction方法async,则可以删除Task.Run部分,因为它会占用线程池中的线程。