I'm not sure there's a perfect solution, yeah. Cancellation is definitely not something you want every listener to be responsible for in a multiple-listener scenario - most scenarios I've dealt with are ones where a single task is responsible for the lifetime of a future - deciding whether to cancel it, etc - usually the task that started it, but other tasks may be monitoring its progress. For example, a simple 'memoization' primitive might subscribe to a future in order to store its result when it is completed, in order to return a cached result the next time. The memoization primitive would never have a reason to cancel the future - that would be up to the task that actually requested the work. So it's tricky.
.NET's standard library uses the 'cancellation token' primitive that Ron described, and I feel that's a pretty low-risk way to encapsulate cancellation, but it loses the benefits of having cancellation baked into the future itself - when I cancel a task via a cancellationtoken, for any subscribers to know about cancellation, I'll have to complete the Future (with some sort of special TaskCancelledError instead of a result?) or drop it on the floor and never complete it. So it creates a need for side-channel communication in all cancellation scenarios, and it requires all consumers to know whether or not a given Future can be cancelled. Maybe this is unavoidable. My particular API approach was simple, albeit not ideal: Since .NET has a 'Disposable' concept, my Future class simply became Disposable. So this meant that in all use cases, the simplest way to get cancellation 'right' was to use the language built-in: var taskFuture = StartSomeTask(); // returns Future using (taskFuture) { // when this block is left, taskFuture is disposed // ... do some async work using taskFuture ... yield return taskFuture; // wait on taskFuture } In practice what this meant is that when the task was suspended to wait on taskFuture, it currently 'owned' the lifetime of that future. As a result, if the *task itself* were cancelled, the task scheduler would dispose the task, and because the task currently owned the lifetime of taskFuture, disposing the task disposed taskFuture. Cancelling an already-complete future in this manner is totally safe, so the 'using' pattern ends up not having any downsides there - my Future implementation is basically first-come-first-serve, where if someone stores a result into a Future before you, they win (and you get an exception for trying to complete it twice), and if you cancel after a result has been stored into it the cancel is a no-op. If you wanted to go further with the design of a task scheduler you could automatically cancel any futures a task is waiting on, but I decided not to do that since I didn't have an opportunity to think through all the consequences. Essentially in my model, the v1.0 equivalent had tri-state Futures: Incomplete, CompletedWithResult, and CompletedWithError. Cancellation was introduced in a later rev of the API and added a fourth 'Disposed' state. >From a callback perspective I ended up with two callbacks, one for 'completion' (either with result or error - the premise being that if you handle one you always want to handle the other) and another for cancellation. The split between functions that affect a Future and functions that consume it is definitely an interesting one. To be honest, my API never made the distinction - a Future is always read/write, and the state change model generally ensures that if the Future is mishandled, an exception will be thrown somewhere to notify you that you screwed up. But I think that capability split is probably important, and I don't know how cancellation fits into that model - in particular since ES6/ES7 seem very focused on using object capability as a security model, you don't want passing a Future across a boundary to give some third party the ability to fake the result of a network request or something like that. On Fri, Apr 19, 2013 at 3:50 PM, Tab Atkins Jr. <jackalm...@gmail.com>wrote: > On Fri, Apr 19, 2013 at 3:35 PM, Kevin Gadd <kevin.g...@gmail.com> wrote: > > My solution for cancellation has been to allow cancellation > notifications to > > be bidirectional - that is, when you subscribe to completion > notifications > > on a Future, you can also subscribe to cancellation notifications. Then > it's > > possible to cancel a given future without breaking any other listeners > (as > > long as they subscribed to cancellation notifications if they care about > > cancellation). Has that been considered? I can see how it might be too > > finicky for the average developer; losing out on cancellation really > sucks > > though. > > > > In particular it feels more important to have explicit cancellation built > > into the object representing work if you can in JS, since there's no way > to > > lean on the garbage collector to cancel work - in environments like > Python > > you can make cancellation implicit by doing it when the Future > representing > > the work is collected, but in JS that's impossible, so having an explicit > > way to dispose of a future is valuable, even if in many cases the > > cancellation doesn't do anything. It's also particularly good in terms of > > encapsulation - if there's a general cancellation mechanism that is > > well-factored, you can just universally make a habit of cancelling > unneeded > > futures, and any backend implementations that support cancellation will > > automatically get told to cancel and save cycles/bandwidth. It means that > > you don't have to go add cancellation in 'after the fact' when the > source of > > a Future changes from a local buffer to a network operation, or remove > > cancellation when you replace a network operation with a cache. > > > > Any kind of task scheduler like dherman's task.js can easily leverage > this > > to automatically cancel any task represented by a cancelled Future, and > in > > particular, task schedulers can propagate cancellation, by cancelling > any of > > the Futures a task is waiting on when the task is cancelled. This has a > very > > desirable property of allowing you to cancel a huge, amorphous blob of > > pending work when it becomes unnecessary by simply cancelling the root - > for > > example in one application I worked on, we kicked off a task to represent > > each avatar in a 3D scene that was responsible for loading the avatar's > > textures, meshes, etc. If the user left the scene before the avatar was > > fully loaded, all we had to do was cancel the task and any pending > texture > > loads or network requests automatically stopped. Getting that right by > hand > > would have been much more difficult, and we wouldn't have necessarily > known > > to build cancellation explicitly into that API when we started. > > I'm curious about what sort of API you use for this. Right now, > Futures are pretty easy to use, because there are only two useful > signals that a callback has to give - successful completion or error - > and it can do this by either returning or throwing. It seems like > we've run out of basic syntax to use for this kind of message-passing, > though, and going any further would require a dedicated object. > > Maybe this can just be done by a second argument that is given to the > callbacks, with a messaging object similar to the resolver object sent > to the resolver callback? > > We could use this object to hold accept/reject/resolve functions too, > in case it's convenient to be more explicit about signaling these. > Then, though, we'd have to be careful to separate the semantics of > "functions that affect the output future" and "functions that talk to > the input future". > > ~TJ > -- -kg
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss