I would like to have one specific thread, queue for Tasks and process tasks in that separate thread. The application would make Tasks based on users usage and queue them into task queue. Then the separate thread processes the tasks. It is vital to keep the thread alive and use it for processing queued tasks even if queue is empty.
I have tried several implementations of TaskScheduler with BlockingCollection and limit the concurrency to only one thread but it seems the Thread gets disposed when queue gets empty and the Task is processed by other thread.
Can you please at least refer me to some sources how to achieve this goal?
tl;dr
Trying to limit one specific thread to process tasks which are dynamically added to the queue.
Edit1:
This is experimental web app that uses WCF and .NET framework 4.6. In the WCF library, I am trying to implement this behaviour with one thread processing tasks. This one thread must init prolog using external dll library and then do work with prolog. If other thread is used in process, library throws AccessViolationException. I've done some research and this is most probably because of badly managed threading in that library. I had implementation where I had locks everywhere and it worked. I am now trying to reimplement and make it asynchronous so I don't block the main thread with locking.
I am not at my computer but I provide some code when I get home later today.
Your approach seems fine, so you probably just made some tiny stupid mistake.
It's actually pretty easy to make a simple custom TaskScheduler. For your case:
void Main()
{
var cts = new CancellationTokenSource();
var myTs = new SingleThreadTaskScheduler(cts.Token);
myTs.Schedule(() =>
{ Print("Init start"); Thread.Sleep(1000); Print("Init done"); });
myTs.Schedule(() => Print("Work 1"));
myTs.Schedule(() => Print("Work 2"));
myTs.Schedule(() => Print("Work 3"));
var lastOne = myTs.Schedule(() => Print("Work 4"));
Print("Starting TS");
myTs.Start();
// Wait for all of them to complete...
lastOne.GetAwaiter().GetResult();
Thread.Sleep(1000);
// And try to schedule another
myTs.Schedule(() => Print("After emptied")).GetAwaiter().GetResult();
// And shutdown; it's also pretty useful to have the
// TaskScheduler return a "complete task" to await
myTs.Complete();
Print("On main thread again");
}
void Print(string str)
{
Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, str);
Thread.Sleep(100);
}
public sealed class SingleThreadTaskScheduler : TaskScheduler
{
[ThreadStatic]
private static bool isExecuting;
private readonly CancellationToken cancellationToken;
private readonly BlockingCollection<Task> taskQueue;
public SingleThreadTaskScheduler(CancellationToken cancellationToken)
{
this.cancellationToken = cancellationToken;
this.taskQueue = new BlockingCollection<Task>();
}
public void Start()
{
new Thread(RunOnCurrentThread) { Name = "STTS Thread" }.Start();
}
// Just a helper for the sample code
public Task Schedule(Action action)
{
return
Task.Factory.StartNew
(
action,
CancellationToken.None,
TaskCreationOptions.None,
this
);
}
// You can have this public if you want - just make sure to hide it
private void RunOnCurrentThread()
{
isExecuting = true;
try
{
foreach (var task in taskQueue.GetConsumingEnumerable(cancellationToken))
{
TryExecuteTask(task);
}
}
catch (OperationCanceledException)
{ }
finally
{
isExecuting = false;
}
}
// Signaling this allows the task scheduler to finish after all tasks complete
public void Complete() { taskQueue.CompleteAdding(); }
protected override IEnumerable<Task> GetScheduledTasks() { return null; }
protected override void QueueTask(Task task)
{
try
{
taskQueue.Add(task, cancellationToken);
}
catch (OperationCanceledException)
{ }
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// We'd need to remove the task from queue if it was already queued.
// That would be too hard.
if (taskWasPreviouslyQueued) return false;
return isExecuting && TryExecuteTask(task);
}
}
It's pretty easy to modify this to give you full control on where the task scheduler is actually executing the task - in fact, I've adapted this from a previous task scheduler I've used which simply had the RunOnCurrentThread method public.
For your case, where you always want to stick to just the one thread, the approach in SingleThreadTaskScheduler is probably better. Although this also has its merits:
// On a new thread
try
{
InitializeProlog();
try
{
myTs.RunOnCurrentThread();
}
finally
{
ReleaseProlog();
}
}
catch (Exception ex)
{
// The global handler
}
Related
Recently, i had a requirement to queue async tasks and i was introduced to BlockingCollection in this link
Queuing asynchronous task in C#
It worked and i'm having a slight change in requirement and need your guidance. I'm using the BlockingCollection as in #Stephen Cleary answer
This is the BlockingCollection from that link
public sealed class ExecutionQueue
{
//private readonly BlockingCollection<Func<Task>> _queue = new BlockingCollection<Func<Task>>();//commented this
private readonly BlockingCollection<Task> _queue = new BlockingCollection<Task>();
public ExecutionQueue() => Complete = Task.Run(() => ProcessQueueAsync());
public Task Completion { get; }
public void Complete() => _queue.CompleteAdding();
private async Task ProcessQueueAsync()
{
foreach (var value in _queue.GetConsumingEnumerable())
await value();
}
}
//public Task Run(Func<Task> lambda)
public Task Run(<Task> lambda)
{
var tcs = new TaskCompletionSource<object>();
_queue.Add(lamda);
return tcs.Task;
}
I need to queue certain DataBase tasks which is within a regular void method. I may not be able to change the signature of this method. How do i do them?
public static ExecutionQueue taskQueue = new ExecutionQueue();
private void SaveValesToDB(...)
{
var item = GetID(...);
...
taskQueue.Run(Task.Run(() =>
{
DBInstance.DBSaveValue1(...); // is it correct to wrap with Task.Run and add to queue? it should be queued and run asynchronously
});
...
}
We save and retrieve data from DB on and off. So, when we queue a DB call that is returning something like a getter, we want to ensure that until we receive the return value we don't process other items that are queued.
private void SaveValesToDB(...)
{
...
taskQueue.Run(Task.Run(() =>
{
DBInstance.DBSaveValue1(...); // is this correct? it should be queued and run asynchronously
});
...
taskQueue.Run(Task.Run(() =>
{
var result1 = DBInstance.DBGetValue2(...); // should be queued and run asynchronously;
LogData(result1);// not a DB call but believe it should be wrapped in here for the result1, correct?
});
/*so in above Task.Run, i want to ensure that until i receive result1
i don't process other items in the queue even
if they are added. how can i do that ?
The main thread should continue. */
...
var result 2 = DBInstance.DBGetValue3(...); // should be queued and run asynchronously
UpdateAdvancedLod(result1 +" "+result2);// here, should i block main thread until i get result1 ?
}
How to handle errors?
Please, guide me.
Edited:
if using Func<Task> in public Task Run(Func<Task> lambda) then is the below correct?
taskQueue.Run(async () =>
{
await Task.Run(() =>
{
DBInstance.DBSaveValue1(...);//is this correct
});
}
);
You could add this method to Stephen Cleary's ExecutionQueue class:
public Task Run(Action action)
{
return Run(() => Task.Run(action));
}
This is an overload of the existing public Task Run(Func<Task> lambda) method. This one delegates the execution of the supplied action to a ThreadPool thread.
Usage example:
var id = GetID();
var task = taskQueue.Run(() => DBInstance.DBSaveValue1(id));
await task; // Optional
Update: To propagate error notifications to the main thread, you could enhance the ExecutionQueue class with an Error event, which would be invoked in the captured context (captured at the time that the instance was created).
private readonly SynchronizationContext _capturedContext;
public event EventHandler<Exception> Error;
public ExecutionQueue() // Constructor
{
_capturedContext = SynchronizationContext.Current ?? new SynchronizationContext();
Completion = Task.Run(() => ProcessQueueAsync());
}
private void OnError(Exception ex)
{
var handler = Error; if (handler == null) return;
_capturedContext.Post(_ => handler.Invoke(this, ex), null);
}
The OnError should be called from inside the catch (Exception ex) block. This will work with Windows Forms apps and WPF apps, because their UI thread is equipped with a SynchronizationContext. It will not work with a Console app because there is no SynchronizationContext there (the Error event
will be raised in a random ThreadPool thread).
I've updated an old project that was using SuperSocket to connect to old c++ servers. With the latest version (from 0.7.0 => 0.8.0.8) I got en exception when trying to reconnect (It says that the socket was opened on a different thread) I would like to have a class that enqueues tasks (First connection and reconnection) and runs them on a specific Thread.
I've seen this approach but when I try to run the task created as got an exception
ExecuteTask may not be called for a task which was previously queued to a different TaskScheduler.
Here's the class I've taken from the link above
public class SameThreadTaskScheduler : TaskScheduler, IDisposable
{
#region publics
public SameThreadTaskScheduler(string name)
{
scheduledTasks = new Queue<Task>();
threadName = name;
}
public override int MaximumConcurrencyLevel => 1;
public void Dispose()
{
lock (scheduledTasks)
{
quit = true;
Monitor.PulseAll(scheduledTasks);
}
}
#endregion
#region protected overrides
protected override IEnumerable<System.Threading.Tasks.Task> GetScheduledTasks()
{
lock (scheduledTasks)
{
return scheduledTasks.ToList();
}
}
protected override void QueueTask(Task task)
{
if (myThread == null)
myThread = StartThread(threadName);
if (!myThread.IsAlive)
throw new ObjectDisposedException("My thread is not alive, so this object has been disposed!");
lock (scheduledTasks)
{
scheduledTasks.Enqueue(task);
Monitor.PulseAll(scheduledTasks);
}
}
public void Queue(Task task)
{
QueueTask(task);
}
protected override bool TryExecuteTaskInline(Task task, bool task_was_previously_queued)
{
return false;
}
#endregion
private readonly Queue<System.Threading.Tasks.Task> scheduledTasks;
private Thread myThread;
private readonly string threadName;
private bool quit;
private Thread StartThread(string name)
{
var t = new Thread(MyThread) { Name = name };
using (var start = new Barrier(2))
{
t.Start(start);
ReachBarrier(start);
}
return t;
}
private void MyThread(object o)
{
Task tsk;
lock (scheduledTasks)
{
//When reaches the barrier, we know it holds the lock.
//
//So there is no Pulse call can trigger until
//this thread starts to wait for signals.
//
//It is important not to call StartThread within a lock.
//Otherwise, deadlock!
ReachBarrier(o as Barrier);
tsk = WaitAndDequeueTask();
}
for (;;)
{
if (tsk == null)
break;
TryExecuteTask(tsk);
lock (scheduledTasks)
{
tsk = WaitAndDequeueTask();
}
}
}
private Task WaitAndDequeueTask()
{
while (!scheduledTasks.Any() && !quit)
Monitor.Wait(scheduledTasks);
return quit ? null : scheduledTasks.Dequeue();
}
private static void ReachBarrier(Barrier b)
{
if (b != null)
b.SignalAndWait();
}
}
Here's how I call the task
public void RegisterServers()
{
sameThreadTaskScheduler.Queue(new Task(() =>
{
...something
}));
What am I doing wrong?
You must start a task to bind it to a TaskScheduler. In your code, you're manually queuing the task, doing it the other way around, so the task doesn't get bound to your task scheduler (or any at all), and TryExecuteTask will fail with that error. The description is rather cryptic, as any actual TaskScheduler is different from null.
For a task that you created without running, there are the overloads of Task.RunSynchronously and Task.Start that take a TaskScheduler.
For a task that starts running automatically, there are the overloads of TaskFactory.StartNew and TaskFactory<TResult>.StartNew that take a TaskScheduler.
For continuation tasks, there are the overloads of Task.ContinueWith, Task<TResult>.ContinueWith, TaskFactory.ContinueWhenAll and TaskFactory.ContinueWhenAny that take a TaskScheduler.
The overloads that don't take a task scheduler are equivalent to specifying TaskScheduler.Current. In the case of TaskFactory, this is true for the default Task.Factory or one created without a task scheduler at the time that the factory's method is called, otherwise the factory's task scheduler is used.
Contrast with the overloads of the newer Task.Run, which always use TaskScheduler.Default. According to most experienced folks, the thread-pool scheduler is desired more often as the default than TaskScheduler.Current which may be thread-bound, but it was too late to change the contract for the existing APIs.
here's my story: I have wcf service. It receives request with work to do. Each task is inserted into blocking queue. The server will take items from this queue periodically and do the work (totally async in different thread). In my "Do" service I need to know when "my" task was done. Like this:
public bool Do(int input)
{
// 1. Add task to the BlockingCollection queue
// 2. Block this thread from returning and observe/wait til my task is finished
return true;
}
Here's my suggestion/solution:
public bool Do(int input)
{
// 1. Create a ManualResetEvent object
// 2. Add this object to task
// 3. Add task to the BlockingCollection queue
// 4. Block this thread from returning - wait for ManualResetEvent object
return true;
}
So, there will be as many ManualResetEvent objects as there are tasks to do. I will literally have an array of sync objects. Is it good solution for my problem?
Or is there better synchronization class to use in my case? Like Wait and Pulse?
Thanks for help,
I'm sorry for the title. I didn't know how to ask this question in the title.
Your plan is good, however I would suggest not tying up a dedicated thread waiting for the work to be done. Switching from a new ManualResetEvent(false) to a new SemephoreSlim(0,1) will let you use WaitAsync() which would allow you to use async/await in your Do method and freeing up the thread to do other work. (UPDATE: This really should be a TaskCompletionSource instead of a Semaphore Slim, but I will not update this example, see the 2nd part below)
public async Task<bool> Do(int input)
{
using(var completion = new new SemephoreSlim(0,1))
{
var job = new JobTask(input, completion);
_workQueue.Add(job);
await completion.WaitAsync().ConfigureAwait(false);
return job.ResultData;
}
}
private void ProcessingLoop()
{
foreach(var job in _workQueue.GetConsumingEnumerable())
{
job.PerformWork(); //Inside PerformWork there is a _completion.Release(); call.
}
}
To make everything self contained you can change the SemaphoreSlim / TaskCompletionSource and put it inside the job then just return the job itself.
public JobTask Do(int input)
{
var job = new JobTask(input);
_workQueue.Add(job);
return job;
}
public class JobTask
{
private readonly int _input;
private readonly TaskCompletionSource<bool> _completionSource;
public JobTask(int input)
{
_input = input;
_completionSource = new TaskCompletionSource<bool>();
}
public void PerformWork()
{
try
{
// Do stuff here with _input.
_completionSource.TrySetResult(true);
}
catch(Exception ex)
{
_completionSource.TrySetException(ex);
}
}
public Task<bool> Work { get { return _completionSource.Task; } }
}
Let's say I have a UI thread and a background thread that subscribe to a custom thread-safe ObservableCollection that I created so that whenever the collection changes it executes the callback within the appropriate context.
Now let's say I add something to the collection (from either thread, doesn't matter which one) and it now has to marshall the callback to both threads. To execute the callback within the UI's context I can simply do a Dispatcher.Invoke(...) and it executes the callback within the UI's context; great.
Now I want to execute the callback within the background thread's context (don't ask me why, it may well be that whatever it's accessing is affinitized to that specific thread or has thread-local storage it needs to access); how would I do that?
Background threads don't have a dispatcher/message pumping mechanism so I can't use a dispatcher or SynchronizationContext, so how would one interrupt a background thread and have it execute my callback within its context?
EDIT: I keep getting answers that are obviously wrong so I must not have explained myself correctly. Forget the UI thread and UI dispatchers guys, they were meant to marshall calls to the UI thread, that's it! Imagine two worker threads A and B. If A modifies my collection then A is in charge of marshalling the callback to itself and to B. Executing the callback within A's context is easy since A was the one triggering it : simply call the delegate in place. Now A needs to marshall the callback to B... now what? Dispatcher and SynContext are useless in this situation.
A good idea might also be extending your own TaskScheduler, you will have to implement three methods:
QueueTask, TryExecuteTaskInline and GetScheduledTasks
you can read about it here
That way, anytime you need to run something on your dedicated thread you could just do:
Task.Factory.StartNew(() => { SomeAction }, SomeCancellationToken, TaskCreationOptions
new MyTaskSchedular());
and have it execute on your thread.
We have a component that must always run on the same STA background thread. We've achieved this by writing our own SynchronizationContext. This article is very helpful.
To summarise, you don't want to interrupt your worker thread, you want it to sit idle waiting for the next task that it should execute. You add jobs to a queue and it processes those jobs in order. The SynchronizationContext is a convenient abstraction around that idea. The SynchronizationContext is the owner of the worker thread - and the outside world does not interact with the thread directly: callers who want to execute a task on the worker thread make the request to the context which adds the job to the job queue. The worker is either working or polling the queue until another job is added, at which point it begins working again.
Update
Here is an example:
using System.Collections.Concurrent;
using System.Threading;
class LoadBalancedContext : SynchronizationContext
{
readonly Thread thread1;
readonly Thread thread2;
readonly ConcurrentQueue<JobInfo> jobs = new ConcurrentQueue<JobInfo>();
public LoadBalancedContext()
{
this.thread1 = new Thread(this.Poll) { Name = "T1" };
this.thread2 = new Thread(this.Poll) { Name = "T2" };
this.thread1.Start();
this.thread2.Start();
}
public override void Post(SendOrPostCallback d, object state)
{
this.jobs.Enqueue(new JobInfo { Callback = d, State = state });
}
void Poll()
{
while (true)
{
JobInfo info;
if (this.jobs.TryDequeue(out info))
{
info.Callback(info.State);
}
Thread.Sleep(100);
}
}
class JobInfo
{
public SendOrPostCallback Callback { get; set; }
public object State { get; set; }
}
}
Usage:
var context = new LoadBalancedContext();
SendOrPostCallback callback = x =>
{
Trace.WriteLine(Thread.CurrentThread.Name);
Thread.Sleep(200);
};
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
Thread.Sleep(1000);
The Send case is slightly more involved as you will need to listen for a reset event.. This is not production quality, but should give you an idea ow what you need to do.
Hope that helps.
Forget dispatcher.invoke, forget the ui thread. Imagine I have 2 worker
threads and I want to dispatch my event to both worker threads; what
can I use?
I'd use two task schedulers for this (as #YuvalItzchakov's answer suggests), one for each thread. I'd also use a custom synchronization context for the worker thread, as #TheMouthofaCow's answer suggests.
That is, for a UI thread, I'd just save and use TaskScheduler.FromCurrentSynchronizationContext(). For the worker thread, I would start a thread and install a custom synchronization context on it, then use FromCurrentSynchronizationContext too.
Something like this (untested):
// UI thread
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
using (var worker = new ThreadWithPumpingSyncContext())
{
// call the worker thread
var result = await worker.Run(async () =>
{
// worker thread
await Task.Delay(1000);
// call the UI thread
await Task.Factory.StartNew(async () =>
{
// UI thread
await Task.Delay(2000);
MessageBox.Show("UI Thread!"),
// call the worker thread
await worker.Run(() =>
{
// worker thread
Thread.Sleep(3000)
});
// UI thread
await Task.Delay(4000);
}, uiTaskScheduler).Unwrap();
// worker thread
await Task.Delay(5000);
return Type.Missing; // or implement a non-generic version of Run
});
}
// ...
// ThreadWithSerialSyncContext renamed to ThreadWithPumpingSyncContext
class ThreadWithPumpingSyncContext : SynchronizationContext, IDisposable
{
public readonly TaskScheduler Scheduler; // can be used to run tasks on the pumping thread
readonly Task _mainThreadTask; // wrap the pumping thread as Task
readonly BlockingCollection<Action> _actions = new BlockingCollection<Action>();
// track async void methods
readonly object _lock = new Object();
volatile int _pendingOps = 0; // the number of pending async void method calls
volatile TaskCompletionSource<Empty> _pendingOpsTcs = null; // to wait for pending async void method calls
public ThreadWithPumpingSyncContext()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
_mainThreadTask = Task.Factory.StartNew(() =>
{
try
{
SynchronizationContext.SetSynchronizationContext(this);
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
// pumping loop
foreach (var action in _actions.GetConsumingEnumerable())
action();
}
finally
{
SynchronizationContext.SetSynchronizationContext(null);
}
}, TaskCreationOptions.LongRunning);
Scheduler = tcs.Task.Result;
}
// SynchronizationContext methods
public override SynchronizationContext CreateCopy()
{
return this;
}
public override void OperationStarted()
{
lock (_lock)
{
if (_pendingOpsTcs != null && _pendingOpsTcs.Task.IsCompleted)
throw new InvalidOperationException("OperationStarted"); // shutdown requested
_pendingOps++;
}
}
public override void OperationCompleted()
{
lock (_lock)
{
_pendingOps--;
if (0 == _pendingOps && null != _pendingOpsTcs)
_pendingOpsTcs.SetResult(Empty.Value);
}
}
public override void Post(SendOrPostCallback d, object state)
{
_actions.Add(() => d(state));
}
public override void Send(SendOrPostCallback d, object state)
{
throw new NotImplementedException("Send");
}
// Task start helpers
public Task Run(Action action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler);
}
public Task Run(Func<Task> action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler).Unwrap();
}
public Task<T> Run<T>(Func<Task<T>> action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler).Unwrap();
}
// IDispose
public void Dispose()
{
var disposingAlready = false;
lock (_lock)
{
disposingAlready = null != _pendingOpsTcs;
if (!disposingAlready)
{
// do not allow new async void method calls
_pendingOpsTcs = new TaskCompletionSource<Empty>();
if (0 == _pendingOps)
_pendingOpsTcs.TrySetResult(Empty.Value);
}
}
// outside the lock
if (!disposingAlready)
{
// wait for pending async void method calls
_pendingOpsTcs.Task.Wait();
// request the end of the pumping loop
_actions.CompleteAdding();
}
_mainThreadTask.Wait();
}
struct Empty { public static readonly Empty Value = default(Empty); }
}
This give you some sort of cooperative asynchronous execution between two threads.
I have a UI which spawns off a background worker thread which performs a complex tree of tasks and sub-tasks that takes about a minute to complete.
A requirement is that the background worker task must be capable of being cancelled once it has begun.
At the moment my solution is naive and makes the code a mess. When a cancel button is pressed in the UI, a cancel token is set. The worker thread periodically (between tasks) polls this token and if it is set, it exits:
void ThreadWorkerHandler(CancelToken cancelToken)
{
DoTask1(cancelToken);
if (cancelToken.IsSet)
return;
DoTask2(cancelToken);
if (cancelToken.IsSet)
return;
DoTask3(cancelToken);
if (cancelToken.IsSet)
return;
DoTask4(cancelToken);
}
void DoTask2(CancelToken cancelToken)
{
DoSubTask2a();
if (cancelToken.IsSet)
return;
DoSubTask2b();
if (cancelToken.IsSet)
return;
DoSubTask2c();
if (cancelToken.IsSet)
return;
}
Is there a better solution? I was toying for something like a SoLongAs statement that would automatically pepper the checks in and automatically and raise an internal exception if the condition was met, which would be internally caught at the end of the loop, eg:
void ThreadWorkerHandler(CancelToken cancelToken)
{
SoLongAs (canelToken.IsSet == false)
{
DoTask1(cancelToken);
DoTask2(cancelToken);
DoTask3(cancelToken);
DoTask4(cancelToken);
}
}
But I imagine that wouldn't work for some reason, also more importantly I doubt something like this actually exists. If not is there a better way to handle this scenario than I am currently using? Thanks.
If you have a collection of delegates that represent your work you can get something that looks pretty close to your code snippet. It has a bit more overhead than your intented syntax, but the key point is that it's a constant overhead, rather than a per-line overhead.
List<Action> actions = new List<Action>()
{
()=> DoTask1(cancelToken),
()=> DoTask2(cancelToken),
()=> DoTask3(cancelToken),
()=> DoTask4(cancelToken),
};
foreach(var action in actions)
{
if (!cancelToken.IsSet)
action();
}
You can use CancellationToken.ThrowIfCancellationRequested(). this will throw exception if token was set.
Also consider using TPL Tasks. All subtasks can be chained one after another with same CancellationToken, this would simplify your code, as TPL framework would take care about checking Token state before invoking continuation.
Your code would looks like this:
Task.Factory.StartNew(DoTask1, cancelationToken)
.ContinueWith(t => DoTask2(), cancelationToken)
.ContinueWith(t => DoTask3(), cancelationToken)
.ContinueWith(t => DoTask4(), cancelationToken)
Note this solution supposing that DoTask<i> will not throw other exceptions except OperationCanceledException.
Note2 you don't have to call ThrowIfCancellationRequested() inside Tasks/subTasks body. TPL will automatically check token state before invoking any continuations. But you can use this method to interrupt execution of task/subtask.
Servy's idea is very good. I'm just stealing it (with all credit to him!) and demonstrating how to use it with an extension method for List<Action>. I'll fully understand anyone that thinks this is "too cute", but I think it has a certain elegance.
Here's an exerpt that show how you can use the extension method. The extension takes a list of Action delegates and runs each one in turn until finished or cancelled, as per Servy's idea.
private static bool test(CancellationToken cancelToken)
{
return new List<Action>
{
doTask1,
doTask2,
doTask3,
doTask4,
() => Console.WriteLine("Press a key to exit.")
}
.Run(cancelToken);
}
And here's the entire sample:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
internal class Program
{
private static void Main(string[] args)
{
CancellationTokenSource cancelSource = new CancellationTokenSource();
Console.WriteLine("Press any key to interrupt the work.");
var work = Task<bool>.Factory.StartNew(() => test(cancelSource.Token));
Console.ReadKey();
cancelSource.Cancel();
Console.WriteLine(work.Result ? "Completed." : "Interrupted.");
}
private static bool test(CancellationToken cancelToken)
{
return new List<Action>
{
doTask1,
doTask2,
doTask3,
doTask4,
() => Console.WriteLine("Press a key to exit.")
}
.Run(cancelToken);
}
private static void doTask1()
{
Console.WriteLine("Task 1 Working...");
Thread.Sleep(1000);
Console.WriteLine("...did some work.");
}
private static void doTask2()
{
Console.WriteLine("Task 2 Working...");
Thread.Sleep(1000);
Console.WriteLine("...did some work.");
}
private static void doTask3()
{
Console.WriteLine("Task 3 Working...");
Thread.Sleep(1000);
Console.WriteLine("...did some work.");
}
private static void doTask4()
{
Console.WriteLine("Task 4 Working...");
Thread.Sleep(1000);
Console.WriteLine("...did some work.");
}
}
public static class EnumerableActionExt
{
public static bool Run(this IEnumerable<Action> actions, CancellationToken cancelToken)
{
foreach (var action in actions)
{
if (!cancelToken.IsCancellationRequested)
{
action();
}
else
{
return false;
}
}
return true;
}
}
}