I have one process generating work and a second process with a BlockingCollection<> that consumes that work. When I close my program, I need my consumer to stop consuming work, but I still need to quickly log the work that was pending but hadn't been consumed.
Right now, my consumer spawns a thread that has a foreach (<object> in BlockingCollection.GetConsumingEnumerable()) loop. When I stop my program, my producer calls Consumer.BlockingCollection.CompleteAdding(). What I find is that my consumer continues to process everything in the queue.
Googling the issues tells me that I need to use a CancellationToken. So I tried that out:
private void Process () { // This method runs in a separate thread
try {
foreach (*work* in BlockingCollection.GetConsumingEnumerable(CancellationToken)) {
// Consume
}
}
catch (OperationCancelledException) {
foreach (*work* in BlockingCollection.GetConsumingEnumerable()) {
// quickly log
}
}
}
My producer has:
private CancellationTokenSource StopFlag = new CancellationTokenSource ();
MyConsumer.CancellationToken = StopFlag.Token;
// Make the consumer spawn it's consuming thread...
StopFlag.Cancel ();
MyConsumer.BlockingCollection.CompleteAdding ();
When I try this, I get no indication that the OperationCancelledException ever happened.
This question tries to explain the use of a cancellation token, but it seems like it's not using it correctly. (Argument: if it works, then it's "correct enough".) And this question would appear to be an exact duplicate of my question but with no example. (Same here.)
So to reiterate: How do I properly use a CancellationToken on BlockingCollection.GetConsumingEnumerable() with the caveat that I need to process the remaining items in the queue after it gets cancelled using a different method?
(I think my problem is centered around the proper use of the CancellationToken. None of my testing indicates that the process is actually being cancelled. (StopFlag.IsCancellationRequested always equals false.))
When you pass in the CancellationToken to GetConsumingEnumerable it won't throw an exception of cancellation is requested, it'll just stop spitting out items. Rather than catching the exception just check the token:
foreach (var item in BlockingCollection.
GetConsumingEnumerable(CancellationToken))
{
//consume item
}
if (CancellationToken.IsCancellationRequested)
foreach (var item in BlockingCollection)
{
//log item
}
Also note that, if cancellation is requested, and it's possible that CompletedAdding hasn't been called then you should just iterate the collection, not call GetConsumingEnumerable. If you know that the producer will complete adding when the operation is cancelled then that's not a problem.
My problem was in how I was trying to cancel the operation. Instead of having my producer owning the CancellationTokenSource, I put it all in the consumer.
public class cProducer {
private cConsumer myConsumer = new cConsumer ();
public void onStart () {
myConsumer.OnStart ();
}
public void onStop () {
myConsumer.OnStop ();
}
public void OnOrderReceived (cOrder newOrder) {
myConsumer.orderQueue.Add (cOrder);
}
}
public class cConsumer {
private CancellationTokenSource stopFlag;
public BlockingCollection<cOrder> orderQueue = new BlockingCollection<cOrder> ();
private Task processingTask;
public void OnStart () {
stopFlag = new CancellationTokenSource ();
processingTask = Task.Factory.StartNew (() => Process ());
}
public void OnStop () {
stopFlag.Cancel ();
orderQueue.CompleteAdding ();
processingTask.Wait ();
}
private void Process () {
try {
foreach (cOrder newOrder in orderQueue.GetConsumingEnumerable (stopFlag.Token)) {
// process
}
}
catch (OperationCanceledException) {
foreach (cOrder cancelledOrder in orderQueue.GetConsumingEnumerable ()) {
// log it
}
}
}
}
I had the exact same problem. The BlockingCollection seemed to be deadlocked when I cancelled the procedure. The OperationCanceledException was not propagated to the calling method. I figured out that my Producer did not take the cancellation token in consideration and was therefore waiting for the queue to be consumed. All I had to do was to provide the cancellation token in the Add() method. To translate this into Jason's solution above, all I did was this:
public void OnOrderReceived (cOrder newOrder, CancellationToken cancellationToken)
{
myConsumer.orderQueue.Add (cOrder, cancellationToken);
}
The Process() method does not need any try-catch clause. However you need to throw if the process is cancelled:
private void Process ()
{
foreach (cOrder newOrder in orderQueue.GetConsumingEnumerable (stopFlag.Token))
{
// process
stopFlag.Token.ThrowIfCancellationRequested();
}
}
Related
I have a system with 10 machines where I need to perform a certain task on each machine one by one in synchronize order. Basically only one machine should do that task at a particular time. We already use Consul for some other purpose but I was thinking can we use Consul to do this as well?
I read more about it and it looks like we can use leader election with consul where each machine will try to acquire lock, do the work and then release the lock. Once work is done, it will release the lock and then other machine will try to acquire lock again and do the same work. This way everything will be synchronized one machine at a time.
I decided to use this C# PlayFab ConsulDotNet library which already has this capability built in looks like but if there is any better option available I am open to that as well. Below Action method in my code base is called on each machine at the same time almost through a watcher mechanism.
private void Action() {
// Try to acquire lock using Consul.
// If lock acquired then DoTheWork() otherwise keep waiting for it until lock is acquired.
// Once work is done, release the lock
// so that some other machine can acquire the lock and do the same work.
}
Now inside that above method I need to do below things -
Try to acquire lock. If you cannot acquire the lock wait for it since other machine might have grabbed it before you.
If lock acquired then DoTheWork().
Once work is done, release the lock so that some other machine can acquire the lock and do the same work.
Idea is all 10 machines should DoTheWork() one at a time in synchronize order. Based on this blog and this blog I decided to modify their example to fit our needs -
Below is my LeaderElectionService class:
public class LeaderElectionService
{
public LeaderElectionService(string leadershipLockKey)
{
this.key = leadershipLockKey;
}
public event EventHandler<LeaderChangedEventArgs> LeaderChanged;
string key;
CancellationTokenSource cts = new CancellationTokenSource();
Timer timer;
bool lastIsHeld = false;
IDistributedLock distributedLock;
public void Start()
{
timer = new Timer(async (object state) => await TryAcquireLock((CancellationToken)state), cts.Token, 0, Timeout.Infinite);
}
private async Task TryAcquireLock(CancellationToken token)
{
if (token.IsCancellationRequested)
return;
try
{
if (distributedLock == null)
{
var clientConfig = new ConsulClientConfiguration { Address = new Uri("http://consul.host.domain.com") };
ConsulClient client = new ConsulClient(clientConfig);
distributedLock = await client.AcquireLock(new LockOptions(key) { LockTryOnce = true, LockWaitTime = TimeSpan.FromSeconds(3) }, token).ConfigureAwait(false);
}
else
{
if (!distributedLock.IsHeld)
{
await distributedLock.Acquire(token).ConfigureAwait(false);
}
}
}
catch (LockMaxAttemptsReachedException ex)
{
//this is expected if it couldn't acquire the lock within the first attempt.
Console.WriteLine(ex.Stacktrace);
}
catch (Exception ex)
{
Console.WriteLine(ex.Stacktrace);
}
finally
{
bool lockHeld = distributedLock?.IsHeld == true;
HandleLockStatusChange(lockHeld);
//Retrigger the timer after a 10 seconds delay (in this example). Delay for 7s if not held as the AcquireLock call will block for ~3s in every failed attempt.
timer.Change(lockHeld ? 10000 : 7000, Timeout.Infinite);
}
}
protected virtual void HandleLockStatusChange(bool isHeldNew)
{
// Is this the right way to check and do the work here?
// In general I want to call method "DoTheWork" in "Action" method itself
// And then release and destroy the session once work is done.
if (isHeldNew)
{
// DoTheWork();
Console.WriteLine("Hello");
// And then were should I release the lock so that other machine can try to grab it?
// distributedLock.Release();
// distributedLock.Destroy();
}
if (lastIsHeld == isHeldNew)
return;
else
{
lastIsHeld = isHeldNew;
}
if (LeaderChanged != null)
{
LeaderChangedEventArgs args = new LeaderChangedEventArgs(lastIsHeld);
foreach (EventHandler<LeaderChangedEventArgs> handler in LeaderChanged.GetInvocationList())
{
try
{
handler(this, args);
}
catch (Exception ex)
{
Console.WriteLine(ex.Stacktrace);
}
}
}
}
}
And below is my LeaderChangedEventArgs class:
public class LeaderChangedEventArgs : EventArgs
{
private bool isLeader;
public LeaderChangedEventArgs(bool isHeld)
{
isLeader = isHeld;
}
public bool IsLeader { get { return isLeader; } }
}
In the above code there are lot of pieces which might not be needed for my use case but idea is same.
Problem Statement
Now in my Action method I would like to use above class and perform the task as soon as lock is acquired otherwise keep waiting for the lock. Once work is done, release and destroy the session so that other machine can grab it and do the work. I am kinda confuse on how to use above class properly in my below method.
private void Action() {
LeaderElectionService electionService = new LeaderElectionService("data/process");
// electionService.LeaderChanged += (source, arguments) => Console.WriteLine(arguments.IsLeader ? "Leader" : "Slave");
electionService.Start();
// now how do I wait for the lock to be acquired here indefinitely
// And once lock is acquired, do the work and then release and destroy the session
// so that other machine can grab the lock and do the work
}
I recently started working with C# so that's why kinda confuse on how to make this work efficiently in production by using Consul and this library.
Update
I tried with below code as per your suggestion and I think I tried this earlier as well but for some reason as soon as it goes to this line await distributedLock.Acquire(cancellationToken);, it just comes back to main method automatically. It never moves forward to my Doing Some Work! print out. Does CreateLock actually works? I am expecting that it will create data/lock on consul (since it is not there) and then try to acquire the lock on it and if acquired, then do the work and then release it for other machines?
private static CancellationTokenSource cts = new CancellationTokenSource();
public static void Main(string[] args)
{
Action(cts.Token);
Console.WriteLine("Hello World");
}
private static async Task Action(CancellationToken cancellationToken)
{
const string keyName = "data/lock";
var clientConfig = new ConsulClientConfiguration { Address = new Uri("http://consul.test.host.com") };
ConsulClient client = new ConsulClient(clientConfig);
var distributedLock = client.CreateLock(keyName);
while (true)
{
try
{
// Try to acquire lock
// As soon as it comes to this line,
// it just goes back to main method automatically. not sure why
await distributedLock.Acquire(cancellationToken);
// Lock is acquired
// DoTheWork();
Console.WriteLine("Doing Some Work!");
// Work is done. Jump out of loop to release the lock
break;
}
catch (LockHeldException)
{
// Cannot acquire the lock. Wait a while then retry
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
}
catch (Exception)
{
// TODO: Handle exception thrown by DoTheWork method
// Here we jump out of the loop to release the lock
// But you can try to acquire the lock again based on your requirements
break;
}
}
// Release and destroy the lock
// So that other machine can grab the lock and do the work
await distributedLock.Release(cancellationToken);
await distributedLock.Destroy(cancellationToken);
}
IMO, LeaderElectionService from those blogs is an overkill in your case.
Update 1
There is no need to do while loop because:
ConsulClient is local variable
No need to check IsHeld property
Acquire will block indefinitely unless
Set LockTryOnce true in LockOptions
Set timeout to CancellationToken
Side note, it is not necessary to invoke Destroy method after you call Release on the distributed lock (reference).
private async Task Action(CancellationToken cancellationToken)
{
const string keyName = "YOUR_KEY";
var client = new ConsulClient();
var distributedLock = client.CreateLock(keyName);
try
{
// Try to acquire lock
// NOTE:
// Acquire method will block indefinitely unless
// 1. Set LockTryOnce = true in LockOptions
// 2. Pass a timeout to cancellation token
await distributedLock.Acquire(cancellationToken);
// Lock is acquired
DoTheWork();
}
catch (Exception)
{
// TODO: Handle exception thrown by DoTheWork method
}
// Release the lock (not necessary to invoke Destroy method),
// so that other machine can grab the lock and do the work
await distributedLock.Release(cancellationToken);
}
Update 2
The reason why OP's code just returns back to Main method is that, Action method is not awaited. You can use async Main if you use C# 7.1, and put await on Action method.
public static async Task Main(string[] args)
{
await Action(cts.Token);
Console.WriteLine("Hello World");
}
I have the following method:
public async Task ScrapeObjects(int page = 1)
{
try
{
while (!isObjectSearchCompleted)
{
..do calls..
}
}
catch (HttpRequestException ex)
{
Thread.Sleep(TimeSpan.FromSeconds(60));
ScrapeObjects(page);
Log.Fatal(ex, ex.Message);
}
}
I call this long running method async and I don't wait for it to finish. Thing is that an exception my occur and in that case I want to handle it. But then I want to start from where I left and with the same thread. At the current state a new thread gets used when I recursively call the method after handling the exception. I would like to keep using the same thread. Is there a way to do so? Thank you!
You probably need to move the try/catch block inside the while loop, and add a counter with the errors occurred, to bail out in case of continuous faulted attempts.
public async Task ScrapeObjects()
{
int failedCount = 0;
int page = 1;
while (!isObjectSearchCompleted)
{
try
{
//..do calls..
}
catch (HttpRequestException ex)
{
failedCount++;
if (failedCount < 3)
{
Log.Info(ex, ex.Message);
await Task.Delay(TimeSpan.FromSeconds(60));
}
else
{
Log.Fatal(ex, ex.Message);
throw; // or return;
}
}
}
}
As a side note it is generally better to await Task.Delay instead of Thread.Sleep inside asynchronous methods, to avoid blocking a thread without a reason.
One simple question before you read the long answer below:
Why you need the same thread? Are you accessing thread static / contextual data?
If yes, there will be ways to solve that easily than limiting your tasks to run on the same thread.
How to limit tasks to run on a single thread
As long as you use async calls on the default synchronization context, and as soon as the code is resumed from an await, it is possible that the thread can change after an await. This is because the default context schedules tasks to the next available thread in the thread pool. Like in the below case, before can be different from after:
public async Task ScrapeObjects(int page = 1)
{
var before = Thread.CurrentThread.ManagedThreadId;
await Task.Delay(1000);
var after = Thread.CurrentThread.ManagedThreadId;
}
The only reliable way to guarantee that your code could come back on the same thread is to schedule your async code onto a single threaded synchronization context:
class SingleThreadSynchronizationContext : SynchronizationContext
{
private readonly BlockingCollection<Action> _actions = new BlockingCollection<Action>();
private readonly Thread _theThread;
public SingleThreadSynchronizationContext()
{
_theThread = new Thread(DoWork);
_theThread.IsBackground = true;
_theThread.Start();
}
public override void Send(SendOrPostCallback d, object state)
{
// Send requires run the delegate immediately.
d(state);
}
public override void Post(SendOrPostCallback d, object state)
{
// Schedule the action by adding to blocking collection.
_actions.Add(() => d(state));
}
private void DoWork()
{
// Keep picking up actions to run from the collection.
while (!_actions.IsAddingCompleted)
{
try
{
var action = _actions.Take();
action();
}
catch (InvalidOperationException)
{
break;
}
}
}
}
And you need to schedule ScrapeObjects to the custom context:
SynchronizationContext.SetSynchronizationContext(new SingleThreadSynchronizationContext());
await Task.Factory.StartNew(
() => ScrapeObjects(),
CancellationToken.None,
TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
TaskScheduler.FromCurrentSynchronizationContext()
).Unwrap();
By doing that, all your async code shall be scheduled to the same context, and run by the thread on that context.
However
This is typically dangerous, as you suddenly lose the ability to use the thread pool. If you block the thread, the entire async operation is blocked, meaning you will have deadlocks.
I want to open a thread to do the things it needs to do until a new command is given by the user. Then this thread should either close or receive a new command.
I have seen many posts that sending a variable to a running thread is hard, that is why I decided to kill the thread and start it again with the new variable.
I used the following post: https://stackoverflow.com/a/1327377 but without success. When I start the thread again (after it has done abort()) it gives me an exception: System.Threading.ThreadStateException.
private static Thread t = new Thread(Threading);
private static bool _running = false;
static void Main(string[] args)
{
[get arg]
if (CanRedo(arg))
{
if (t.IsAlive)
{
_running = false;
t.Interrupt();
if (t.Join(2000)) // with a '!' like in the post, abort() would not be called
{
t.Abort();
}
}
_running = true;
t.Start(arg); // gives System.Threading.ThreadStateException
}
}
private static void Threading(object obj)
{
_stopped = false;
string arg = obj.ToString();
while(_running)
{
if (bot._isDone)
{
ExecuteInstruction(arg);
}
}
}
What am I doing wrong?
I'm going to guess that you don't literally mean to abort the thread and start that same thread again. That's because if we start a thread to do some work we don't care which thread it is. If you cancel one thing and start something else, you probably don't care if it's the same thread or a different one. (In fact it's probably better if you don't care. If you need precise control over which thread is doing what then something has gotten complicated.) You can't "abort" a thread and restart it anyway.
Regarding Thread.Abort:
The Thread.Abort method should be used with caution. Particularly when you call it to abort a thread other than the current thread, you do not know what code has executed or failed to execute when the ThreadAbortException is thrown, nor can you be certain of the state of your application or any application and user state that it is responsible for preserving. For example, calling Thread.Abort may prevent static constructors from executing or prevent the release of unmanaged resources.
It's like firing an employee by teleporting them out of the building without warning. What if they were in the middle of a phone call or carrying a stack of papers? That might be okay in an emergency, but it wouldn't be a normal way to operate. It would be better to let the employee know that they need to wrap up what they're doing immediately. Put down what you're carrying. Tell the customer that you can't finish entering their order and they'll need to call back.
You're describing an expected behavior, so it would be better to cancel the thread in an orderly way.
That's where we might use a CancellationToken. In effect you're passing an object to the thread and telling it to check it from time to time to see if it should cancel what it's doing.
So you could start your thread like this:
class Program
{
static void Main(string[] args)
{
using (var cts = new CancellationTokenSource())
{
ThreadPool.QueueUserWorkItem(DoSomethingOnAnotherThread, cts.Token);
// This is just for demonstration. It allows the other thread to run for a little while
// before it gets canceled.
Thread.Sleep(5000);
cts.Cancel();
}
}
private static void DoSomethingOnAnotherThread(object obj)
{
var cancellationToken = (CancellationToken) obj;
// This thread does its thing. Once in a while it does this:
if (cancellationToken.IsCancellationRequested)
{
return;
}
// Keep doing what it's doing.
}
}
Whatever the method is that's running in your separate thread, it's going to check IsCancellationRequested from time to time. If it's right in the middle of doing something it can stop. If it has unmanaged resources it can dispose them. But the important thing is that you can cancel what it does in a predictable way that leaves your application in a known state.
CancellationToken is one way to do this. In other really simple scenarios where the whole thing is happening inside one class you could also use a boolean field or property that acts as a flag to tell the thread if it needs to stop. The separate thread checks it to see if cancellation has been requested.
But using the CancellationToken makes it more manageable if you want to refactor and now the method executing on another thread is a in separate class. When you use a known pattern it makes it easier for the next person to understand what's going on.
Here's some documentation.
What about doing it this way:
private static Task t = null;
private static CancellationTokenSource cts = null;
static void Main(string[] args)
{
[get arg]
if (CanRedo(out var arg))
{
if (t != null)
{
cts.Cancel();
t.Wait();
}
// Set up a new task and matching cancellation token
cts = new CancellationTokenSource();
t = Task.Run(() => liveTask(arg, cts.Token));
}
}
private static void liveTask(object obj, CancellationToken ct)
{
string arg = obj.ToString();
while(!ct.IsCancellationRequested)
{
if (bot._isDone)
{
ExecuteInstruction(arg);
}
}
}
Tasks are cancellable, and I can see nothing in your thread that requires the same physical thread to be re-used.
In a thread, I create some System.Threading.Task and start each task.
When I do a .Abort() to kill the thread, the tasks are not aborted.
How can I transmit the .Abort() to my tasks ?
You can't. Tasks use background threads from the thread pool. Also canceling threads using the Abort method is not recommended. You may take a look at the following blog post which explains a proper way of canceling tasks using cancellation tokens. Here's an example:
class Program
{
static void Main()
{
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
Task.Factory.StartNew(() =>
{
while (true)
{
// do some heavy work here
Thread.Sleep(100);
if (ct.IsCancellationRequested)
{
// another thread decided to cancel
Console.WriteLine("task canceled");
break;
}
}
}, ct);
// Simulate waiting 3s for the task to complete
Thread.Sleep(3000);
// Can't wait anymore => cancel this task
ts.Cancel();
Console.ReadLine();
}
}
Like this post suggests, this can be done in the following way:
int Foo(CancellationToken token)
{
Thread t = Thread.CurrentThread;
using (token.Register(t.Abort))
{
// compute-bound work here
}
}
Although it works, it's not recommended to use such approach. If you can control the code that executes in task, you'd better go with proper handling of cancellation.
Aborting a Task is easily possible if you capture the thread in which the task is running in. Here is an example code to demonstrate this:
void Main()
{
Thread thread = null;
Task t = Task.Run(() =>
{
//Capture the thread
thread = Thread.CurrentThread;
//Simulate work (usually from 3rd party code)
Thread.Sleep(1000);
//If you comment out thread.Abort(), then this will be displayed
Console.WriteLine("Task finished!");
});
//This is needed in the example to avoid thread being still NULL
Thread.Sleep(10);
//Cancel the task by aborting the thread
thread.Abort();
}
I used Task.Run() to show the most common use-case for this - using the comfort of Tasks with old single-threaded code, which does not use the CancellationTokenSource class to determine if it should be canceled or not.
This sort of thing is one of the logistical reasons why Abort is deprecated. First and foremost, do not use Thread.Abort() to cancel or stop a thread if at all possible. Abort() should only be used to forcefully kill a thread that is not responding to more peaceful requests to stop in a timely fashion.
That being said, you need to provide a shared cancellation indicator that one thread sets and waits while the other thread periodically checks and gracefully exits. .NET 4 includes a structure designed specifically for this purpose, the CancellationToken.
I use a mixed approach to cancel a task.
Firstly, I'm trying to Cancel it politely with using the Cancellation.
If it's still running (e.g. due to a developer's mistake), then misbehave and kill it using an old-school Abort method.
Checkout an example below:
private CancellationTokenSource taskToken;
private AutoResetEvent awaitReplyOnRequestEvent = new AutoResetEvent(false);
void Main()
{
// Start a task which is doing nothing but sleeps 1s
LaunchTaskAsync();
Thread.Sleep(100);
// Stop the task
StopTask();
}
/// <summary>
/// Launch task in a new thread
/// </summary>
void LaunchTaskAsync()
{
taskToken = new CancellationTokenSource();
Task.Factory.StartNew(() =>
{
try
{ //Capture the thread
runningTaskThread = Thread.CurrentThread;
// Run the task
if (taskToken.IsCancellationRequested || !awaitReplyOnRequestEvent.WaitOne(10000))
return;
Console.WriteLine("Task finished!");
}
catch (Exception exc)
{
// Handle exception
}
}, taskToken.Token);
}
/// <summary>
/// Stop running task
/// </summary>
void StopTask()
{
// Attempt to cancel the task politely
if (taskToken != null)
{
if (taskToken.IsCancellationRequested)
return;
else
taskToken.Cancel();
}
// Notify a waiting thread that an event has occurred
if (awaitReplyOnRequestEvent != null)
awaitReplyOnRequestEvent.Set();
// If 1 sec later the task is still running, kill it cruelly
if (runningTaskThread != null)
{
try
{
runningTaskThread.Join(TimeSpan.FromSeconds(1));
}
catch (Exception ex)
{
runningTaskThread.Abort();
}
}
}
To answer Prerak K's question about how to use CancellationTokens when not using an anonymous method in Task.Factory.StartNew(), you pass the CancellationToken as a parameter into the method you're starting with StartNew(), as shown in the MSDN example here.
e.g.
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Task.Factory.StartNew( () => DoSomeWork(1, token), token);
static void DoSomeWork(int taskNum, CancellationToken ct)
{
// Do work here, checking and acting on ct.IsCancellationRequested where applicable,
}
You should not try to do this directly. Design your tasks to work with a CancellationToken, and cancel them this way.
In addition, I would recommend changing your main thread to function via a CancellationToken as well. Calling Thread.Abort() is a bad idea - it can lead to various problems that are very difficult to diagnose. Instead, that thread can use the same Cancellation that your tasks use - and the same CancellationTokenSource can be used to trigger the cancellation of all of your tasks and your main thread.
This will lead to a far simpler, and safer, design.
Tasks have first class support for cancellation via cancellation tokens. Create your tasks with cancellation tokens, and cancel the tasks via these explicitly.
You can use a CancellationToken to control whether the task gets cancelled. Are you talking about aborting it before it's started ("nevermind, I already did this"), or actually interrupting it in middle? If the former, the CancellationToken can be helpful; if the latter, you will probably need to implement your own "bail out" mechanism and check at appropriate points in the task execution whether you should fail fast (you can still use the CancellationToken to help you, but it's a little more manual).
MSDN has an article about cancelling Tasks:
http://msdn.microsoft.com/en-us/library/dd997396.aspx
Task are being executed on the ThreadPool (at least, if you are using the default factory), so aborting the thread cannot affect the tasks. For aborting tasks, see Task Cancellation on msdn.
I tried CancellationTokenSource but i can't do this. And i did do this with my own way. And it works.
namespace Blokick.Provider
{
public class SignalRConnectProvider
{
public SignalRConnectProvider()
{
}
public bool IsStopRequested { get; set; } = false; //1-)This is important and default `false`.
public async Task<string> ConnectTab()
{
string messageText = "";
for (int count = 1; count < 20; count++)
{
if (count == 1)
{
//Do stuff.
}
try
{
//Do stuff.
}
catch (Exception ex)
{
//Do stuff.
}
if (IsStopRequested) //3-)This is important. The control of the task stopping request. Must be true and in inside.
{
return messageText = "Task stopped."; //4-) And so return and exit the code and task.
}
if (Connected)
{
//Do stuff.
}
if (count == 19)
{
//Do stuff.
}
}
return messageText;
}
}
}
And another class of the calling the method:
namespace Blokick.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MessagePerson : ContentPage
{
SignalRConnectProvider signalR = new SignalRConnectProvider();
public MessagePerson()
{
InitializeComponent();
signalR.IsStopRequested = true; // 2-) And this. Make true if running the task and go inside if statement of the IsStopRequested property.
if (signalR.ChatHubProxy != null)
{
signalR.Disconnect();
}
LoadSignalRMessage();
}
}
}
You can abort a task like a thread if you can cause the task to be created on its own thread and call Abort on its Thread object. By default, a task runs on a thread pool thread or the calling thread - neither of which you typically want to abort.
To ensure the task gets its own thread, create a custom scheduler derived from TaskScheduler. In your implementation of QueueTask, create a new thread and use it to execute the task. Later, you can abort the thread, which will cause the task to complete in a faulted state with a ThreadAbortException.
Use this task scheduler:
class SingleThreadTaskScheduler : TaskScheduler
{
public Thread TaskThread { get; private set; }
protected override void QueueTask(Task task)
{
TaskThread = new Thread(() => TryExecuteTask(task));
TaskThread.Start();
}
protected override IEnumerable<Task> GetScheduledTasks() => throw new NotSupportedException(); // Unused
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException(); // Unused
}
Start your task like this:
var scheduler = new SingleThreadTaskScheduler();
var task = Task.Factory.StartNew(action, cancellationToken, TaskCreationOptions.LongRunning, scheduler);
Later, you can abort with:
scheduler.TaskThread.Abort();
Note that the caveat about aborting a thread still applies:
The Thread.Abort method should be used with caution. Particularly when you call it to abort a thread other than the current thread, you do not know what code has executed or failed to execute when the ThreadAbortException is thrown, nor can you be certain of the state of your application or any application and user state that it is responsible for preserving. For example, calling Thread.Abort may prevent static constructors from executing or prevent the release of unmanaged resources.
You can use this class..:
It works for all typs of returned Values..
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace CarNUChargeTester
{
public class TimeOutTaskRunner<T>
{
private Func<T> func;
private int sec;
private T result;
public TimeOutTaskRunner(Func<T> func, int sec)
{
this.func = func;
this.sec = sec;
}
public bool run()
{
var scheduler = new SingleThreadTaskScheduler();
Task<T> task = Task<T>.Factory.StartNew(func, (new CancellationTokenSource()).Token, TaskCreationOptions.LongRunning, scheduler);
if (!task.Wait(TimeSpan.FromSeconds(sec)))
{
scheduler.TaskThread.Abort();
return false;
}
result = task.Result;
return true;
}
public T getResult() { return result; }
}
class SingleThreadTaskScheduler : TaskScheduler
{
public Thread TaskThread { get; private set; }
protected override void QueueTask(Task task)
{
TaskThread = new Thread(() => TryExecuteTask(task));
TaskThread.Start();
}
protected override IEnumerable<Task> GetScheduledTasks() => throw new NotSupportedException();
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException();
}
}
To use it you can write:
TimeOutTaskRunner<string> tr = new TimeOutTaskRunner<string>(f, 10); // 10 sec to run f
if (!tr.run())
errorMsg("TimeOut"); !! My func
tr.getResult() // get the results if it done without timeout..
I have a task that launches several child tasks. (e.g., Task A creates B,C,D,E,F). I also create a System.Threading.Timer to poll a database every 10 seconds to check if the scheduled item was cancelled by request. If it does, it sets CancellationTokenSource so that the task knows to cancel. Each sub-task, in this case B,C,D,E,F, will cancel when appropriate (they are looping thru files and moving them around).
Since Task implements IDisposable, I want to know if it is a good idea to call Task.WaitAll again from the catch block, to wait for the cancellations to propogate. While the cancellation request will be processed, the sub-tasks may be in the middle of a loop and can't cancel until that completes
However, per MSDN:
Always call Dispose before you release your last reference to the Task. Otherwise, the resources it is using will not be freed until the garbage collector calls the Task object's Finalize method.
Should I call wait again on my task array in order to properly call Dispose() on each task in the array?
public class MyCancelObject
{
CancellationTokenSource Source { get;set;}
int DatabaseId { get;set;}
}
private void CheckTaskCancelled(object state)
{
MyCancelObject sourceToken = (MyCancelObject)state;
if (!sourceToken.CancelToken.IsCancellationRequested)
{
//Check database to see if cancelled -- if so, set to cancelled
sourceToken.CancelToken.Cancel();
}
}
private void SomeFunc()
{
Task.StartNew( () =>
{
MyCancelObject myCancelObject = new MyCancelObject(
databaseId,
new CancellationTokenSource());
System.Threading.Timer cancelTimer = new Timer(
new TimerCallback(CheckIfTaskCancelled),
myCancelObject,
10000,
10000);
Task[] someTasks = new Task[someNumberOfTasks];
for (int i = 0; i < someNumberOfTasks; i++)
someTasks[i] = Task.Factory.StartNew(
() =>
{
DoSomeWork(someObject, myCancelObject.CancelToken.Token);
},
TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning,
myCancelObject.CancelToken.Token);
try
{
Task.WaitAll(someTasks, cts);
}
catch (AggregateException)
{
//Do stuff to handle
}
catch (OperationCanceledException)
{
//Should I call Task.WaitAll(someTasks) again??
//I want to be able to dispose.
}
}
}
I feel like I have figured this out, but anyone that would like to add anything else useful is more than welcome.
I simply called Task.WaitAll() again from the catch block to wait for the other tasks to finish. After they have all finished, I have a finally block cleaning up all tasks in the array.
try
{
Task.WaitAll(someTaskArray, cancelToken)
}
catch (OperationCanceledException)
{
Task.WaitAll(someTaskArray);
}
finally
{
for (int i = 0; i < someTaskArray.Length; i++)
someTaskArray[i].Dispose();
}