3

In my Asp.Net MVC 5 project I have a ~3 minute task that I pass to Task.Factory.StartNew().

I would like to pause the task from within the task if there is a validation issue in one of the steps of my code running in the task. I don't want to delay it async because the rest of the task will continue to run, which can't happen.

Could I use thread.sleep() without any repercussions since I'm within a task? I read that I may have to use TaskScheduler.Default to have the Task.Factory create a new thread for each task.

I'm using a PauseToken similar to a CancellationToken so I'll be able to resume the task or cancel this task based on user input.

Multithreading really scares me, and I don't want to overlook something.

Here is an example of the Thread.Sleep implementation:

public void WaitIfPaused(PauseToken pauseToken, CancellationToken cancellationToken, IProgressBar progressBar)
    {
        //TODO: If paused for an hour notify user via noty and abort the task to keep it from completing by cancellation via cancellationToken.
        //wait for 1 hour
        for (int i = 0; i < 3600; i++)
        {
            ThrowExceptionIfCancelled(cancellationToken, progressBar);
            if (pauseToken.IsPaused)
            {
                Thread.Sleep(1000);
            }
            else
            {
                break;
            }

        }
    }

PauseToken: http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx


Requested: Implementation of task structure in shared code library.

public void StartTask(params object[] data)
    {
        //throw an exception if no ITask was found
        if (_taskToRun == null)
            throw new Exception("Task cannot be null");

        //set up task cancellation
        CancellationTokenSource = new CancellationTokenSource();
        var cancellationToken = CancellationTokenSource.Token;

        //set up task pausing
        PauseTokenSource = new PauseTokenSource();
        var pauseToken = PauseTokenSource.Token;

        //start a new task using the Task that was set
        _task = Task.Factory.StartNew(() => _taskToRun.Execute(cancellationToken, pauseToken, data), cancellationToken);
}

My Execute method that is invoked by _taskToRun.Execute:

Public override void Execute(CancellationToken cancellationToken, PauseToken pauseToken, params object[] data)
    {
        var managerList = (List<IFileManager>) data[0];
        var instr = (List<InstructionSet>) data[1];

        ProcessInstructions(managerList, instr, cancellationToken, pauseToken);
    }

Update due to comments:

Code example: 3 instructions

For(var instruction in instructions)
{
    instruction.Execute();
}

In my execute method I run into a scenario for pause and call WaitWhilePausedAsync from within the execute. It will continue to execute the other two instructions, but pause the only the current instructions execute method.

Edit: By awaiting instruction.Execute() it will wait until instruction.Execute() completes or is unpaused.


Final Edit: I was able to resolve the issue by awaiting the Execute method and making it async like Servy and I3arnon suggested.

Final Code Sample:

foreach(var instruction in instructions)
{
    try 
    {
        await instruction.Execute(pauseToken);
    }
    catch(InvalidOperationException)
    {
        pauseTokenSource.IsPaused = true;
        //ask if user wants to cancel or resume.
    }
}
//Simplified
public async Task<bool> Execute(PauseToken pauseToken)
{
    await pauseToken.WaitWhilePausedAsync();
    //do work
}
Brandon Tull
  • 337
  • 4
  • 17
  • the pattern you linked to seems to fit you well. Why not use that? – default Jan 27 '15 at 21:28
  • 3
    So you go out of your wait to find a tool that does *exactly* what you want in as effective of a way as it can be done, and then you choose to just not use it and spinwait on a boolean instead? Just call `WaitWhilePausedAsync` and `await` it, just like the blog post demonstrates. That's literally exactly what that was built to do. – Servy Jan 27 '15 at 21:28
  • I can't use WaitWhilePausedAsync because I want the task to be paused from within. For example – Brandon Tull Jan 27 '15 at 21:35
  • @BrandonTull Why? Why do you want to do the pause synchronously, instead of asynchronously? That said, the synchronous counterparts to that can be seen all over the place. They're just basic synchronization primitives, i.e. `AutoResetEvent`. If you want to synchronously block until signaled, then use a tool that synchronously blocks until signaled, rather than a tool that asynchronously waits until signaled that you do a spinwait on until it says its completed. – Servy Jan 27 '15 at 21:37
  • Added comment to main question for formatting. – Brandon Tull Jan 27 '15 at 21:44
  • 1
    Either `Execute` should be asynchronous too, or you simply need to use a synchronous, and not asynchronous, signaling primitive. If you want to use a *synchronous* method to do *synchronous* things why did you go out of your way to find an *asynchronous* signaling primitive? – Servy Jan 27 '15 at 21:47
  • Thank you for your feedback Servy. I would like the overall Task to be asynchronous, but the code inside needs to be run synchronously in my foreach loop. I can't await a single instruction.execute method. There are multiple users that will be firing off sets of instructions wrapped up in a single Task. – Brandon Tull Jan 27 '15 at 21:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/69697/discussion-between-brandon-tull-and-servy). – Brandon Tull Jan 27 '15 at 22:05
  • 1
    @BrandonTull: What problem are you really trying to solve? `StartNew` is almost certainly the wrong approach for an ASP.NET MVC app. – Stephen Cleary Jan 28 '15 at 00:33
  • @StephenCleary Should I use Task.Run or async await instead? – Brandon Tull Jan 28 '15 at 15:27
  • @BrandonTull: Plain `async`/`await` is fine. You should avoid `StartNew` and `Task.Run`. – Stephen Cleary Jan 28 '15 at 15:28

1 Answers1

0

You can safely use Thread.Sleep. The only drawback is that the thread would be wasted blocking synchronously.

You should be using await Task.Delay(1000) instead. The code after that line would not execute until the wait is complete, but you won't be wasting a thread in the meantime:

public async Task WaitIfPausedAsync(PauseToken pauseToken, CancellationToken cancellationToken, IProgressBar progressBar)
{
    for (int i = 0; i < 3600; i++)
    {
        ThrowExceptionIfCancelled(cancellationToken, progressBar);
        if (pauseToken.IsPaused)
        {
            await Task.Delay(1000)
        }
        else
        {
            break;
        }
    }
}

Edit: I was unaware of PauseToken.WaitWhilePausedAsync. You should definitly use that instead of replicating that yourself with polling over PauseToken.IsPaused

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 5
    Or he could just `await WaitWhilePausedAsync`, which does exaclty what he wants, doesn't wait up to a *full second* longer than is needed, doesn't waste a thread in the process, and semantically represents the exact problem. You're going out of your way to not use the tool designed to solve the exact problem at hand, and to instead use an actively worse solution. – Servy Jan 27 '15 at 21:29
  • I added to the bottom of my original question why I was unable to do async pause within my instruction.Execute() methods. – Brandon Tull Jan 27 '15 at 21:47
  • @Servy sure.. I wan't aware of that method. – i3arnon Jan 27 '15 at 21:47
  • @BrandonTull You need to mark these methods as `async` and `await` the returned `Task`. That will enable you to have sequential processing without blocking threads. – i3arnon Jan 27 '15 at 21:49
  • I don't want to mark the execute method async because that needs to run synchronously. There are several instructions wrapped in one Task and those instructions are synchronous. The foreach loop in in executed in my example at the bottom is in task. – Brandon Tull Jan 27 '15 at 21:55
  • 1
    @BrandonTull that doesn't mean you can't use async. You seem to be mixing synchronous and sequential. But if you must be synchronous the use a synchronous pause token – i3arnon Jan 27 '15 at 21:58