Created
October 6, 2020 22:26
-
-
Save quetzalcoatl/10ce9806d97551b8e4a89b4e2d7b9132 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
There are several ways to do that, but first some facts: | |
- your code is executed by some thread, if you did not start more of them, then probably by the main thread of the application | |
- that isPrime function is probably executed by 1 specif thread that may get stuck in that method for a longer time | |
- it's hard to tell a thread to stop doing it's piece of work if that piece of work is not designed to be stoppable | |
Regarding last point: it seems easy - you can just kill the thread (unless it's THE main thread) or you can call thread.Abort(), | |
but both methods are dangerous, and in most cases that's a bad idea, and explaining that is another story. | |
So, let's first focus on "not designed to be stoppable". | |
Imagine .. something silly. Silly solutions are still some solutions. | |
Please tread this code as pseudocode, minor fixes may be needed. | |
public static bool isPrime(BigInteger num) { | |
DateTime startedAt = DateTime.Now; | |
if (num <= 1) return false; | |
if (num == 2) return true; | |
for (int i = 3; i <= num / 2; ++i) | |
{ | |
if (num % i == 0) | |
return false; | |
if(DateTime.Now - startedAt > TimeSpan.FromHours(1)) | |
throw new TimeoutException(); | |
} | |
return true; | |
} | |
I suppose that doesn't need an explanation. And it seems silly. But no doubt it will work just fine. | |
The most important observation here are: | |
- the 'work' must take care of being cancellable on its own, somehow | |
- something must take care of observing how the time passes | |
- the 'work' must have some way of notifying the caller about the time-out | |
Let's assume you don't want to put the burden of observing time in your math algo. | |
Something else will observe the time, and tell the algo to stop at some moment. | |
.Net comes with a CancellationToken/CancellationTokenSource classes. | |
CancellationToken can be basically used as a boolean "stop now" flag: | |
public static bool? isPrime(BigInteger num, CancellationToken ct) { | |
if (num <= 1) return false; | |
if (num == 2) return true; | |
for (int i = 3; i <= num / 2 && !ct.IsCancellationRequested; ++i) | |
if (num % i == 0) | |
return false; | |
return ct.IsCancellationRequested ? null : true; | |
} | |
Naturally, when we break a loop, some result should still be produced, | |
ideally a result that can be distinguished from normal result, so I changed it to bool?/null. | |
CancellationToken also comes with a handy "throw something for me when cancelled" method: | |
public static bool isPrime(BigInteger num, CancellationToken ct) { | |
if (num <= 1) return false; | |
if (num == 2) return true; | |
for (int i = 3; i <= num / 2; ++i) | |
{ | |
ct.ThrowIfCancellationRequested(); | |
if (num % i == 0) | |
return false; | |
} | |
return true; | |
} | |
The exception thrown is OperationCancelledException and it has some handy support from .Net in certain places. | |
Like not killing your application if it bubbles up as unhandled-exception from a side thread, etc. | |
Now, let's use the isPrime with a CancellationToken! | |
void FooBar() | |
{ | |
CancellationTokenSource ctSrc = new CancellationTokenSource(); | |
BigInteger num = ....; | |
bool? result = isPrime(num, ctSrc.Token); | |
Console.WriteLine(...., result); | |
// ctSrc.Cancel(); ?? | |
} | |
That's just that, the Source is a factory/manager object that gives us the tokens, | |
it also has a Cancel() method that marks the tokens as Cancelled, so easy-peasy.. | |
..but who and when will call the 'cancel()' method? | |
We can't cancel before calling isPrime, it would not make any sense. | |
We can't cancel after calling isPrime, because our current thread is busy in that method for the next 3 days. | |
If something is meant to observe time, it cannot be THIS thread. We need ANOTHER one. | |
CancellationTokenSource ctSrc = new CancellationTokenSource(); | |
void FooBar() | |
{ | |
BigInteger num = ....; | |
Thread watchdog = new Thread(Watchdog); | |
watchdog.Start(); | |
bool? result = isPrime(num, ctSrc.Token); | |
Console.WriteLine(...., result); | |
// probably kill the watchdog thread if isPrime managed to finish in time | |
} | |
void Watchdog() | |
{ | |
Thread.Sleep( 1 hour ); | |
ctSrc.Cancel(); | |
} | |
I hope what happens here this is more-or-less self-descriptive. | |
Another thread is started and just waits for 1hour then cancels the token. | |
If isPrime didn't finish in time, it will notice the token is cancelled and will stop. | |
If isPrime did finish.. then noone is watching the token anymore. | |
Thread will still wake up after 1 hour and cancel the token though it will have no effect. | |
It might be a good idea to clean up the thread when isPrime finishes in time. | |
That's the absolutely basic general idea about cancellation of any work pieces. | |
If you add to it more tools/libraries/etc like anon-functions, Tasks, Observables, etc | |
it will look more pretty or will take care of the cleanup for you more or less better | |
but at its code, it will stay the same: | |
- something must watch the time | |
- workpiece must discover it's cancelled | |
- it's a good idea to distinguish a cancelled workpiece from finished workpiece |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@zacharba Maybe I chose a wrong word. "Silly" was the first thing that came to my mind and I just later repeated it as a pointer to indicate which code example I refer to..
Instead of "silly", probably "direct" or "trivial" or "straightforward" would be more appropriate. Or something that convey all those three meanings at the same time.
So.. out of a better word, I wrote "silly", but it's definitely not pejorative. It's fine, it works, etc. It's more like calling a kid "silly", because the kid did something but not like "the adults would do". Something along the lines... Because when I see code like that, I know that the most likely reason is that either someone was in hurry and wrote the simplest thing and had no time or motivation to polish it later, or didn't know CancellationTokens, or for whatever reasons ignored that:
async/await
for non-blocking, like Task/IPropertyChanged/etc; seeing a "landmark' class, most programmers immediately know what's going on and will expect these to be used for that precise function of theirs, so not using it feels weird or lackingDoTheTimeyThing(int a, string b, object c)
inside, you don't see that it you may abort it at your discretion, when you seeDoTheTimeyThing(int a, string b, object c, CancellationToken xx)
you have quite a hint..but, there are cases when such "silly/obvious/direct/straighforward/trivial/easy/(...)" solution is fine, it's all tradeoffs. If the code is really non-typical, like, performance-critical, low-on-resources, like can't have that one more watchdog thread, or can't expect thread contexts to be switched regularily, or if there's no chance that the cancellation-reasons ever get more complex, or (..) then it may totally be fine to not use CancellationTokens and use something more basic. Oh. Maybe that would be the best word?