简体   繁体   中英

AsyncController used to run a console application

There's a few questions that address the async controller workings but none quite deal with what I needed. hopefully someone's already done this and can tell me if I'm doing something wrong.

I've got a combination of a web application that deals with configuring tasks and a console application counterpart that deals with running configured tasks. I wanted to be able to run the tasks from web application by clicking a button on which control is immediately returned to the user and the task is executed in the background. AsyncController seems like a perfect match. Both applications access the same database using EF6, Unity dependency injection and SQL Server 2012. Web interface is targeting .NET 4.5 on MVC4.

I can run the console application perfectly fine without a hitch. I can also trigger the run from the web interface using below code. The only problem is when triggered through web application, the task will run to a point (I can see it in the logs (Nlog)) but it stops executing until I rebuild the solution - solution contains both applications, which would also replace the .exe being run. I don't get any pauses when I run the console application directly.

It's my first time using Task and I'm a bit shy with the Process class too. Please don't be too harsh if I'm doing something incredibly stupid.

Here's the code for the controller:

public class TaskRunnerController : AsyncController
{
    private readonly ITaskProcessService _taskProcessService;
    private readonly IAuthenticationContext _context;
    private readonly IServiceBase<Task> _taskService;

    public TaskRunnerController(ITaskProcessService taskProcessService,
        IAuthenticationContext context,
        IServiceBase<Task> taskService)
    {
        _taskProcessService = taskProcessService;
        _context = context;
        _taskService = taskService;
    }

    private string TaskRunnerExe
    {
        get
        {
            var setting = ConfigurationManager.AppSettings["TaskRunner.Exe"];
            Guard.Against<ConfigurationErrorsException>(string.IsNullOrEmpty(setting), "Missing configuration setting: TaskRunner.Exe");
            return setting;
        }
    }

    [CustomAuthorize(typeof(CanRun))]
    public ActionResult RunTaskAsync(long id)
    {
        var task = _taskService.Find(i => i.TaskId == id);
        Guard.AgainstLoad(task, id);
        var fileInfo = new FileInfo(TaskRunnerExe);
        Guard.Against<ConfigurationErrorsException>(!fileInfo.Exists, "TaskRunner could not be found at specified location: {0}", TaskRunnerExe);
        var taskProcess = _taskProcessService.Schedule(task, _context.Principal.Identifier);
        AsyncManager.OutstandingOperations.Increment();
        System.Threading.Tasks.Task.Factory.StartNew(() =>
        {
            ProcessStartInfo info = new ProcessStartInfo(fileInfo.FullName,
                string.Format("{0} {1}", task.TaskId, taskProcess.TaskProcessId));
            info.UseShellExecute = false;
            info.RedirectStandardInput = true;
            info.RedirectStandardOutput = true;
            info.CreateNoWindow = true;
            var process = new Process { StartInfo = info, EnableRaisingEvents = true };
            process.Exited += (sender, e) =>
            {
                AsyncManager.OutstandingOperations.Decrement();
            };
            process.Start();
        });
        if (_context.Principal.HasPermission<CanViewList>())
            return RedirectToAction("Index", "Tasks");
        return RedirectToAction("Index", "Home");
    }

    public ActionResult RunTaskProgress()
    {
        return View();
    }

    public ActionResult RunTaskCompleted()
    {
        return Content("completed", "text/plain");
    }
}

Dependencies:

  • taskProcessService: repository for each event of task runs
  • context: keeps information about the current user
  • taskService: repository for tasks

The console application (.exe) exits when it's completely finished. As I mentioned, when invoked through web application, the task will only finish when I rebuild the application - at this point it does everything it should - it seems like it was working the whole time, it just stopped logging or reporting back at some point in the process.

Perhaps it's important - I had another try at this without the Async controller, but with the same setup - run the Process, wrapped in a task which had the same end effect. I thought the process was being killed when the main thread was returned to the pool, that's why I tried the AsyncController.

If I open task manager I can see the process for the exe sits there idle. The application logs it's progress up to a point but then just sits there. Is this a problem with VS? I'm using VS2012.

Am I wrong to wrap the Process into a Task? Is there a way to run a executable directly via a Task class and the Action class?

Many thanks for any insight.

More than likely, the application is being terminated because the worker process ends. IIS will terminate any threads or child processes running under a requests thread after it has finished.

As such, it's simply not viable to do what you're doing. You could move your console app to a schedule task, and then trigger your scheduled task from web application.

An update on this. I've now established that the exe (console application) being kicked off by the process is indeed running as normal in the background. However for some reason it stops writing to the standard output stream when it's redirected for some reason. I've updated the console application being run so that it adds logs directly to task in database as it progresses and this is working fine. I can track progress of the task on the website in real time.

I've also verified there are no memory leaks (both console application and web site are managed code). Once the exe is finished it is removed from the process list as I would expect.

Point being, the above code works but I'm still not sure why the standard output isn't being written to. It could be because inside the console application, I'm using dependency injected Nlog to write to Console via a ConsoleTarget.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM