1

Suppose I have a long operation inside a subroutine or function and I want to be able to cancel (exit subroutine or function) immediately after a "cancellation flag" is set to true. What is the best way to do it? One way is to check the flag after each line of code but that is not very elegant.

For example:

dim _CancelFlag as boolean = false Sub LongOperation() dim a as integer a = 1 if _CancelFlag = True Then Exit Sub End If a = 2 if _CancelFlag = True Then Exit Sub End If 'And so on... End Sub 

Of course a = 1 is only for illustration purpose. Say the operation is really long until a = 100 and it is not possible to put them into a loop, how can I trigger the cancellation from outside of the subroutine and stop it immediately?

I was thinking to put the sub into a BackgroundWorker or Task but then I still have to check for CancellationToken somewhere inside the sub.. Do I really have to check after each line of code?

8
  • 1
    Does this answer your question? Proper way of cancel execution of a method Commented Feb 21, 2020 at 7:20
  • Yes you have to. the BackgroundWorker to do the lengthy routine, and the main thread to keep the UI responsive including the button for example that will cancel the task by setting the CancelFlag = True in it's Click event. Commented Feb 21, 2020 at 7:24
  • yes, but then the cancellation flag need to be captured inside the backgroundworker DoWork and cancel from inside right? If the sub routine above is inside the BackgroundWorker's DoWork, where should I check the: if worker.CancellationPending = True ? Commented Feb 21, 2020 at 8:16
  • all the cancellation token example is inside loop. the cancellation flag is checked at the end of the loop. this is different from my question. Commented Feb 21, 2020 at 8:18
  • 1
    The whole point is that you shouldn't be doing what you're trying to do. You can Abort a thread but it is generally recommended against because it leaves the system in an unknown state. That reason that you should be checking and cancelling explicitly is so that you know that you've cleaned up whatever needs it. Commented Feb 21, 2020 at 8:37

4 Answers 4

2

It depends on the granularity you want to achieve: how many seconds can you expect your method be canceled?

If the cancellation must take place "immediately" you have to check in as many place as you can. However, just checking before and after long sub steps of your operation is enough in the general case.

Remember that if you have to wait on handles, you have to use the appropriate overload that specifies a timeout or a cancellation token.

Additionally, you should propagate the cancellation token/your flag deep down your methods to allow detection early the cancellation requests.

Sign up to request clarification or add additional context in comments.

5 Comments

in other words, check after each line of code for finest granularity right?
Check after and before lines that invoke long operations which you have not in control. For example, assigning variables virtually have no cost, therefore it is not useful to check after every assignment.
(If I am not clear enough, kindly post an example of a code yours and I'll show you what I was talking about)
yes, I know. I have several loop inside the operation and a lot of low cost operations. I will definitely check cancel flag in loop. The question is the low cost operation. It doesn't cost much time but in my case, it is better to cancel sooner when cancel flag is on. it's plc io operation. it's fast but better if can stop the operation as soon as cancel flag is detected.
I agree. Just one last thing: if you use the flag and multiple threads, be sure to use the volatile pattern.
0

I found a more elegant way to do it, although it does use a loop in the end. Please let me know if anybody has an better solution. I will also update when I find something else.

Sub LongOperation() dim state as integer = 0 Do while state < 100 Select Case state Case 0 a = 1 Case 1 a = 2 Case Else Exit do End Select If _CancelFlag = True Then Exit Sub End If state += 1 Loop End Sub 

Comments

-1

This is a sample windows application I have created to cancel or pause the log running task.

public partial class Form1 : Form { updateUI _updateGUI; CancellationToken _cancelToken; PauseTokenSource _pauseTokeSource; public Form1() { InitializeComponent(); } delegate void updateUI(dynamic value); private void btnStartAsync_Click(object sender, EventArgs e) { _pauseTokeSource = new PauseTokenSource(); _cancelToken = default(CancellationToken); _pauseTokeSource.onPause -= _pauseTokeSource_onPause; _pauseTokeSource.onPause += _pauseTokeSource_onPause; Task t = new Task(() => { LongRunning(_pauseTokeSource); }, _cancelToken); t.Start(); } private void _pauseTokeSource_onPause(object sender, PauseEventArgs e) { var message = string.Format("Task {0} at {1}", e.Paused ? "Paused" : "Resumed", DateTime.Now.ToString()); this.Invoke(_updateGUI, message); } private async void LongRunning(PauseTokenSource pause) { _updateGUI = new updateUI(SetUI); for (int i = 0; i < 20; i++) { await pause.WaitWhilePausedAsync(); Thread.Sleep(500); this.Invoke(_updateGUI, i.ToString() + " => " + txtInput.Text); //txtOutput.AppendText(Environment.NewLine + i.ToString()); if (_cancelToken.IsCancellationRequested) { this.Invoke(_updateGUI, "Task cancellation requested at " + DateTime.Now.ToString()); break; } } _updateGUI = null; } private void SetUI(dynamic output) { //txtOutput.AppendText(Environment.NewLine + count.ToString() + " => " + txtInput.Text); txtOutput.AppendText(Environment.NewLine + output.ToString()); } private void btnCancelTask_Click(object sender, EventArgs e) { _cancelToken = new CancellationToken(true); } private void btnPause_Click(object sender, EventArgs e) { _pauseTokeSource.IsPaused = !_pauseTokeSource.IsPaused; btnPause.Text = _pauseTokeSource.IsPaused ? "Resume" : "Pause"; } } public class PauseTokenSource { public delegate void TaskPauseEventHandler(object sender, PauseEventArgs e); public event TaskPauseEventHandler onPause; private TaskCompletionSource<bool> _paused; internal static readonly Task s_completedTask = Task.FromResult(true); public bool IsPaused { get { return _paused != null; } set { if (value) { Interlocked.CompareExchange(ref _paused, new TaskCompletionSource<bool>(), null); } else { while (true) { var tcs = _paused; if (tcs == null) return; if (Interlocked.CompareExchange(ref _paused, null, tcs) == tcs) { tcs.SetResult(true); onPause?.Invoke(this, new PauseEventArgs(false)); break; } } } } } public PauseToken Token { get { return new PauseToken(this); } } internal Task WaitWhilePausedAsync() { var cur = _paused; if (cur != null) { onPause?.Invoke(this, new PauseEventArgs(true)); return cur.Task; } return s_completedTask; } } public struct PauseToken { private readonly PauseTokenSource m_source; internal PauseToken(PauseTokenSource source) { m_source = source; } public bool IsPaused { get { return m_source != null && m_source.IsPaused; } } public Task WaitWhilePausedAsync() { return IsPaused ? m_source.WaitWhilePausedAsync() : PauseTokenSource.s_completedTask; } } public class PauseEventArgs : EventArgs { public PauseEventArgs(bool paused) { Paused = paused; } public bool Paused { get; private set; } } 

3 Comments

Thank you for sharing but I know this logic already. Your long running task is inside a for loop. In that case, cancellation token is checked every 500ms in your loop. What I am looking is when it is not inside a loop and cancel immediately, if possible.
@AlbertTobing, no prob bro
The majority of the code presented here relates to pausing a task, which was not part of the question. You could have trimmed this down to the key lines for (int i = 0; i < 20; i++) { Thread.Sleep(500); if (_cancelToken.IsCancellationRequested) { } }, but then the question specifically stated "it is not possible to put [the steps of the task] into a loop." So, as much code as was given here, none of it really applies to or answers the question.
-1

If your LongOperation() is well splittable into short operations (I assume a=1, a=2, ..., a=100 being all reasonably short) than you could wrap all the short operations into Tasks, put them into a TaskQueue and process that queue, checking between the Tasks if cancellation was requested.

If LongOperation() is difficult to split you could run the LongOperation() on a separate dedicated thread and abort that thrad on cancellation. Some have commented aborting a thread being dirty and not being recommended. Actually that's not that bad, if properly handled. Aborting a thread just raises a ThradAbortException within the thread method. So if there is a try - catch - finally in the LongOperation(), catching and handling the exception and if the finally code properly does cleanup, closes all handles, disposes etc., this should be ok and nothing to be afraid of.

4 Comments

Except Thread.Abort() is that bad. Among other issues, when you abort a thread you'll have no idea where in the long-running task it was canceled, unless you wrap every step in try { ... } catch (ThreadAbortException) { ... }, in which case we're back where the question started. As Destroying threads states, Abort() is for stopping code "not designed for cooperative cancellation." The real solution? Design your code for cooperative cancellation.
As you can see, my first suggstion was for a cooperation cancellation design ;)
And only if not possible Thread.Abort(), and in that case, of course, every single step or the whole LongOperation() has to be wrapped in try { ... } catch { ... } finally { ... } blocks. I know all those examples, but they seem to revolve around not properly handling within the aborted thread (including Thread.BeginCriticalReagion() and so on ...). Can you give an example, how Thread.Abort() can hurt, when full and correct cleanup and state management handling in the aborted thread is done?
"only if not possible" - Except it is possible. Why is Abort() even in the conversation when we know the author controls both the canceler and the cancelee? Further, you now mention the need for things like "Thread.BeginCriticalReagion() and so on" (without defining "so on"), so can you see how this answer understates the ramifications of Abort()? The issue isn't if Abort() can possibly be used safely, the issue is if it's as simple and "nothing to be afraid of" as you make it out to be. Does jumping through all these hoops really sound anything like the "best way" here, anyways?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.