سؤال

I have a MVC.Net4 Application in which i have Longrunning backround operations which is why i use the System.Threading.Tasks.Task Class.

I start the Tasks after the User clicked a certain Button on the GUI, from that Task im going to use async methods from a intern API which i need to await. This is all working.

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");
}

Now what i really want to do is visualizing the operation. I use jQuery Progressbars on the GUI and my Own ProgressBar Object in the Session for this. I have a List of ProgressBars on my Session and a PartialView strongly Typed to a List of those ProgressBars.

ProgressBar Class:

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>
         }
    }

And then the Object i have in my Session:

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

Whenever i start a new Task i will add another ProgressBar to that List, which works just fine.

No where i get into Troubles is when i want to Remove the ProgressBars from Session again.

In the DoActionkComplete Method which i set in Task.ContinueWith() i want to Remove the ProgressBar corresponding to the finished action. I have the ProgressBar Ready there, its stored in my AsyncReturnValue Class which i have in the Task.Result at this point:

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

In this Method i would like to remove the Progressbar from the Session with HttpContext.GetSession().ProgressBars.Remove(pbToRemove). But the problem with that im still operating on a different Thread so i have no valid HttpContext there and my SessionObject is null on that Thread.

This is what my DoActionComplete Method looks right now:

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"});
        }

I created a Workaround where i have a static List of Progressbars on my Controller. In the DoActionComplete Method i add the ProgressBars i want to delete to that List. I need to use polling (with jQuery $.get() and setinterval) in order to delete them.

I have a custom Class for the DeleteList on which i can set a Username so i know who is the Owner of that ProgressBar and only show it to him, else everyone would see it because its Static.

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

Dont get me wrong, my workaround works just fine but i want to know the clean way. From what i know static Lists on Controllers could technically grow very big and slow the site down. Such Lists also lose its entries when the ApplicationPool restarts the Application.

So my Actual Question would be how can i access a HttpContext SessionObject from a different Thread like i'm using? And if its not possible, what would be the proper Way to achieve what i want?

هل كانت مفيدة؟

المحلول

So my Actual Question would be how can i access a HttpContext SessionObject from a different Thread like i'm using?

That's not possible.

And if its not possible, what would be the proper Way to achieve what i want?

First, let's back up to the original scenario. The problem is here:

I have a MVC.Net4 Application in which i have Longrunning backround operations which is why i use the System.Threading.Tasks.Task Class.

That's the wrong solution for that scenario. The proper solution is to have a reliable queue (Azure queue / MSMQ) with an independent background process (Azure webjob / Win32 service) doing the processing. This is a more reliable solution because any work you toss onto the ASP.NET thread pool may be lost (especially if you don't inform ASP.NET about that work).

Once you have this architecture set up, then you can use SignalR to communicate from your web server to your clients. SignalR will use polling if it has to, but it can also use more efficient methods (such as websockets).

نصائح أخرى

You can specify the SynchroniztionContext that the ContinueWith task continues on and then you should be able to access the progress bars. Try changing your t.ContinueWith(DoActionkComplete); call to

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

If you are using .NET 4.5 you can rewrite your method with 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");

}

And if you make the DoAction method async as well, you can remove the Task.Run part as that uses up a thread from the thread pool.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top