3

Given is a very common threading scenario:

Declaration

private Thread _thread;
private bool _isRunning = false;

Start

_thread = new Thread(() => NeverEndingProc());
thread.Start();

Method

private void NeverEndingProc() {
    while(_isRunning) {
        do();
    }
}

Possibly used in a asynchronous tcp listener that awaits callbacks until it gets stopped by letting the thread run out (_isRunning = false).

Now I'm wondering: Is it possible to do the same thing with Task? Using a CancellationToken? Or are Tasks only for procedures that are expected to end and report status?

Acrotygma
  • 2,531
  • 3
  • 27
  • 54
  • You just want to create an infinite loop with cancellation option using tasks? – Deffiss Nov 12 '13 at 13:40
  • @Deffiss Basically, yes. I was once told are 'just' threads, but on a higher level which behave slightly different, but serve the same purpose. I might be wrong on this, though. – Acrotygma Nov 12 '13 at 13:44
  • I think that `_isRunning` needs to be `volatile`. Otherwise, the compiler might realize it's not modified from the current thread and check it only once before the loop starts. – svick Nov 12 '13 at 14:06

2 Answers2

3

You can certainly do this just by passing NeverEndingProc to Task.Run.

However, there is one important difference in functionality: if an exception is propagated out of NeverEndingProc in a bare Thread, it will crash the process. If it is in a Task, it will raise TaskScheduler.UnobservedException and then be silently ignored (as of .NET 4.5).

That said, there are alternatives you can explore. Reactive Extensions, for example, pretty much removes any need for the "infinite thread loop".

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • `TaskCreationOptions.LongRunning` is necessary here isn't it? So `Factory.StartNew` with `TaskCreationOptions` and "DefaultScheduler" would be the preferable option than `Task.Run`. Correct me if am wrong – Sriram Sakthivel Nov 12 '13 at 13:47
  • Reactive Extensions really sounds promising, but currently I don't have the time to dig into another new tool. :) – Acrotygma Nov 12 '13 at 13:53
  • 1
    @SriramSakthivel: `LongRunning` is just a hint. Unless you're already doing really intensive parallel processing at the time you start the "infinite loop" task, it's unnecessary. And `Task.Run` uses the default scheduler. – Stephen Cleary Nov 12 '13 at 14:04
  • @StephenCleary Am not sure what you mean by "it is unnecessary". Without that ThreadPool will use pool's thread right? enabling that makes dedicated thread for the task, allowing pool's threads to take care of other small cpu intensive operations? – Sriram Sakthivel Nov 12 '13 at 14:08
  • @SriramSakthivel: The semantics of `LongRunning` are an implementation detail. Yes, in .NET 4.0 through 4.5.1, it will use its own thread and not a thread pool thread (but this can change in a future version). If you don't specify `LongRunning`, it will use a thread pool thread, and the thread pool heuristics will automatically detect that it is a long running task and inject a new thread pool thread if necessary. Currently (.NET 4.5 - 4.5.1), the thread pool heuristics will detect this situation in ~2 seconds. – Stephen Cleary Nov 12 '13 at 14:17
  • Oh, That makes sense. Till now I was under the impression that I must use `LongRunning` for time consuming tasks. Thanks for clarification. – Sriram Sakthivel Nov 12 '13 at 14:19
  • Can you confirm that Task.Run() will NEVER raise the task on the same thread as the calling thread, so that the calling thread can always continue immediately in a fire and forget way (not wait for the task to execute synchronously)? In my question here: http://stackoverflow.com/questions/12245935/is-task-factory-startnew-guaranteed-to-use-another-thread-than-the-calling-thr, it was achieved through the use of a CancellationToken with StartNew. – Erwin Mayer Jul 01 '14 at 19:29
  • @ErwinMayer: No; it is certainly possible that `Task.Run` may run on the same thread. However, it would continue the current code first. `Task.Run` is similar to `StartNew` passing `TaskScheduler.Default` as the task scheduler. (I'm assuming that you do *not* block on the resulting task). – Stephen Cleary Jul 02 '14 at 02:42
  • @StephenCleary Thanks, do you know if this is also exactly what passing a CancellationToken does (not guaranteeing that it will be run on a different thread, but guaranteeing the current code continues first)? – Erwin Mayer Jul 02 '14 at 09:02
  • 1
    @ErwinMayer: I believe so, but I'm not entirely sure. – Stephen Cleary Jul 02 '14 at 10:17
1

One reason to use Task + CancellationToken is to make the individual processes and their cancellation more independent of each other. In your example, notice how NeverEndingProc needs a direct reference to the _isRunning field in the same class. Instead, you could accept an external token:

Start:

public void StartNeverEndingProc(CancellationToken token) {
    Task.Factory.StartNew(() => NeverEndingProc(token), token);
}

Method:

private void NeverEndingProc(CancellationToken token) {
    while (true) {
        token.ThrowIfCancellationRequested();
        do();
    }
}

Now cancellation is managed by the caller, and can be applied to multiple independent tasks:

var instance = new YourClass();

var cts = new CancellationTokenSource();

instance.StartNeverEndingProc(cts.Token);  // start your task
StartOtherProc(cts.Token);  // start another task

cts.Cancel();  // cancel both
nmclean
  • 7,564
  • 2
  • 28
  • 37