Closed domenic closed 8 years ago
requested
is commonly used in async functions in .NET, here is some real code we have (some slightly simplified). I posted 3 examples let me know if you need more:
Repeat IEnumerable that stops repeating on cancellation, we have enumerables of functions that we repeat for ongoing tasks, this is a completely synchronous use case:
private static IEnumerable<T> RepeatEnumerable<T>(IEnumerable<T> toRepeat, CancellationToken token)
{
var asList = toRepeat.ToList();
while (!token.IsCancellationRequested)
{
foreach (var item in asList.TakeWhile(item => !token.IsCancellationRequested))
{
yield return item;
}
}
}
function repeat(source, token) {
var asList = [...source];
while(true) {
for(var item of asList) {
if(token.requested) return;
yield item;
}
}
Giving us exit points in code that otherwise doesn't support cancellation, this pattern is quite common in some of our code bases:
public async Task UpdateLiveFeedCache(CancellationToken ct)
{
foreach (var expertType in expertTypeEnums) {
if (ct.IsCancellationRequested) continue;
await LiveFeed.GetLiveFeedOperationsData(from, to, expertType);
}
}
async function update(token) {
for(var value of enumeration) {
if(token.requested) continue;
await expensiveUpdateThatDoesntSupportCancellation(value);
}
}
Cleanup (we'd probably use catch cancel for that in JS):
public async Task UpdateCacheQueue(CancellationToken ct, string message)
await RegularQueue.ClearQueueAsync(ct);
EmailGenerator.MailCacheUpdateStartedBackground(Constants.BackendMailingList);
foreach (var cacheKey in _redisDb.GetCacheKeysIterator().Partition(100)) {
if (ct.IsCancellationRequested) {
await RegularQueue.ClearQueueAsync(); // clear items we've added
await ManagementQueue.Notify(message);
}
RegularQueue.Notify(cacheKey.ToString()))
}
}
async function updateCacheQueue(token) {
try {
for await (const key of _redisDb.getCacheKeysIterator()) {
await queue.notify(key);
// some additional logic
}
} catch cancel {
queue.clear(); // update cache interrupted in the middle
}
}
Repeat IEnumerable that stops repeating on cancellation, we have enumerables of functions that we repeat for ongoing tasks, this is a completely synchronous use case:
This seems better served by using generator.return()
.
Giving us exit points in code that otherwise doesn't support cancellation, this pattern is quite common in some of our code bases:
Thanks, this is similar to the crypto example I have. However it's notable that in both my crypto example and in yours, it would probably be better to do these operations in parallel using Promise.all
.
In my example I used cancelIfRequested()
which I guess uses requested
under the covers.
Cleanup (we'd probably use catch cancel for that in JS):
Yeah, this is a good example of why a special catch block is nice :).
I posted 3 examples let me know if you need more:
More would be nice!
This seems better served by using generator.return().
Yes, probably - generator .return
is essentially cancellation after all. It is exactly that "third state" in some regard. I can come up with examples where I'd want to do cleanup in this case - but I can do that by composing something I'd close with a .return
Thanks, this is similar to the crypto example I have. However it's notable that in both my crypto example and in yours, it would probably be better to do these operations in parallel using Promise.all.
In my example doesn't benefit from doing it in parallel (since it's pushing to the queue) and in fact there is a hard limit on the number of things we can do in parallel with that service- doing more in parallel means more network and more actions at once and would force us to pay more money to Azure for higher tiers.
Here are some more examples:
A "blocking" reader reading. (An await read
where read
waits until cancellation happens):
public async Task<T> Consume(CancellationToken t = default(CancellationToken), bool shouldDeleteMessage = true)
{
T ans;
do
{
ans = await TryConsume(t, shouldDeleteMessage);
if (t.IsCancellationRequested)
{
throw new BusConsumeException("Cancellation requested in consume method of NotificationBus");
}
if (ans == null)
{
await Task.Delay(1000, t);
}
} while (ans == null);
return ans;
}
We need the IsCancellationRequested
because TryConsume
might not do something cancellable in a subclass.
async consume(token) {
do {
ans = await tryConsume(token);
if(ans) return ans;
if(t.requested) throw new Error(":(");
await delay(1000, t);
} while(true);
}
Initializing a batched service, .NET web apps have a QueueBackgroundWorkItem
hook function which schedules a work item to be executed at some point in the future. It takes a cancellation token which is cancelled when the server shuts down. Here is the init
method of a batching component of the notification item.
private void Init()
{
BackgroundWork.QueueWorkItem(async ct =>
{
await NotifyInternal(); // does I/O
if (ct.IsCancellationRequested) return; // don't refresh again
await Task.Delay(TimeSpan.FromMinutes(10), ct);
BackgroundWork.QueueWorkItem(c =>
{ // schedule a refresh of the app, note that this is only recursive because I have to use the platform
Init();
return Task.FromResult(false); // this just pleases the type system
});
});
}
function init() {
setImmediateWithCancellation(async ct => {
await refreshItems();
if(ct.requested) return; // no need to refresh again
await delay(1000 * 60);
setImmediateWithCancellation(init);
});
}
Another good example is last with cancellation where some items do and some don't support cancellation.
A "blocking" reader reading.
Thanks, this one worked out!
Initializing a batched service
This wasn't as clear to me. It seems like just a recursive version of the iterative consume
example?
Another good example is last with cancellation where some items do and some don't support cancellation.
Hmm, how so? Here is how I would envision last
in a world with cancelation:
So e.g. https://gist.github.com/domenic/46776bb71a2f885f79013130cd5301aa.
This wasn't as clear to me. It seems like just a recursive version of the iterative consume example?
Basically, we have a web-server that needs to update a small in-memory cache from storage every 10 minutes. The code has to run on the platform scheduling to not block actual work the server has to do.
It's background work that I need to cancel and I can't write it in a straightforward way with await.
(Optimally, I'd like to be able to configure a scheduler for an async function zone - but that's another story for another idea that's been brewing in my head for a while).
Hmm, how so? Here is how I would envision last in a world with cancelation:
That's how I envision it too assuming operation can be cancelled. What if operation
can be cancelled sometimes? In that case you'd have to guard against functions that are not actually cancellable and check if the token's cancellation is requested and "normalize" the output to be like in a truly cancellable function (Or at least, we needed to in our .NET version of it).
Also pinging @zenparsing since well, cancellation token use cases.
https://github.com/domenic/cancelable-promise/blob/master/Cancel%20Tokens.md is looking OK, so I'll close this. It's still not entirely clear whether/why we need all the different parts of the API, especially if some of the fun in https://github.com/domenic/cancelable-promise/blob/master/New%20Ideas%20Brainstorming.md makes it in, but I guess it's basically exposing all the primitives, so it's fine.
In particular, justifying
cancelIfRequested
andrequested
is not terribly easy. Most of the .NET examples seem to have to do with threading.See https://github.com/domenic/cancelable-promise/blob/master/Cancel%20Tokens.md#for-the-consumer for what I have so far.