[Idea][Swift 3] Change 'DispatchQueue.after' to take a relative time + clock (with default). Maybe replace clocks with an enum?


(Karl) #1

#1 -

Currently, DispatchQueue.after() takes an absolute time as the “after” parameter. This makes it hard to understand how to use it; you need to go digging through generated interfaces to find out what a ‘DispatchTime’ is and how you construct it (by getting the .now() and adding a DispatchTimeInterval using one of the easily-missable operator overloads, if you were wondering).

Here is what DispatchQueue.after looks like now:

public func after(when: DispatchTime, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @convention(block) () -> Void)

So to use it, you have to type:

DispatchQueue.main.after(when: DispatchTime.now() + .milliseconds(250)) { /* do stuff */ }

I don’t believe this is a great fit with the Swift API Guidelines. I believe the name “after” already implies that the time is relative, the argument label should be dropped, and that nuances about the clock (basically, whether or not it pauses during system sleep) are edge-cases which can be handled for the majority of users with a sensible default value (or better yet, modelling via an enum or boolean switch — see #2). Additionally, There are overloads with “wallTime” parameter labels which seem only to duplicate type information (the monotonic and walltime clocks are actually split at the type level) and could be more concise and readable. The principle is that, ultimately, you should just be able to write the above code like this:

DispatchQueue.main.after(.milliseconds(250)) { /* do stuff */ }

Or

DispatchQueue.main.at(DispatchTime.now() + .seconds(3)) { /* do stuff */ }

It’s not something you use all the time (like .sync/.async), but I find that whenever I do need it I’m frustrated by how needlessly complex it is to decipher and use. I would find these methods much more obvious, I could figure out how to use them much more quickly, and I think I’d remember how to use them more quickly.

···

#2 -

Actually, while writing this down it’s becoming clearer and clearer that the idea to split DispatchTime (the monotonic clock) and DispatchWallTime (the wall clock) at the type level is probably a bad idea.

Novice users are not going to understand what’s going on here - I expect most of them to default to the more generic-sounding “DispatchTime” without any idea of the implications of this. Perhaps we should look at replacing these clock variations with a more descriptive enum or boolean, rather than a separate type. For example:

struct DispatchTime { // replaces DispatchTime and DispatchWallTime
    let rawValue : dispatch_time_t
    let clock : Clock

    enum Clock { case monotonicClock; case wallClock }
}

This would not obsolete the discussion at the start about “after”. The name “after” still implies that I want something done at some duration relative to an absolute point in time (usually now).

Thoughts?
There have been some really in-depth naming discussions on here recently, so I’m interested to hear what you make of it.

Karl


(Anthony Chivetta) #2

nuances about the clock (basically, whether or not it pauses during system sleep) are edge-cases which can be handled for the majority of users with a sensible default value

I’m going to stay out of the naming part of the conversation for the moment and just address this point…

I disagree strenuously: it’s a super important distinction that is frequently the cause of subtle but bad bugs!

Lets start with the fact that you (presumably not the novice user you describe below) actually got the difference between the two clocks wrong. Here’s the current behavior (on Darwin, not sure what Linux currently implements):

  - DispatchTime is a clock that only advances forward and tracks the amount of time the computer is awake.

  - DispatchWillTime is a clock that tracks as a UTC clock would, generally moving ahead during sleep, but is *not* monotonic. It can move forward, backward, upside down, whatever it wants. And frequently it will.

And this stuff is hard: if you are writing a tea timer app and you pick either of these, you have a bug. DispatchTime, the bug is that if the user locks their device and it falls asleep the alarm won’t fire upon wake. DispatchWallTime, if the device realizes its clock is ahead or behind the “real” time the elapsed duration won’t be what you expect. (Should we have a third that works for the tea timer? Absolutely…)

So, what’s the sensible default you had in mind that won’t fail for a large portion of use cases? Safety is an important design point in an API surface and making these distinctions clear to developers is absolutely critical to achieving that goal.

(Of course, Foundation.Timer gets all of the above horribly wrong, IMHO. We should fix that too…)

(the monotonic and walltime clocks are actually split at the type level)

DispatchTime is not a monotonic clock. At least, not in the POSIX sense. So let’s not call it that. (Linux’s failure to implement POSIX correctly notwithstanding.)

Novice users are not going to understand what’s going on here - I expect most of them to default to the more generic-sounding “DispatchTime” without any idea of the implications of this.

If that’s the case, that’s a good argument for forcing users to make the choice in an even more obvious and explicit way. Not making it easier to use one or the other when they’ll likely get it wrong.

~ Anthony

···

On Jul 13, 2016, at 9:18 , Karl via swift-evolution <swift-evolution@swift.org> wrote:


(Pierre Habouzit) #3

#1 -

Currently, DispatchQueue.after() takes an absolute time as the “after” parameter. This makes it hard to understand how to use it; you need to go digging through generated interfaces to find out what a ‘DispatchTime’ is and how you construct it (by getting the .now() and adding a DispatchTimeInterval using one of the easily-missable operator overloads, if you were wondering).

Here is what DispatchQueue.after looks like now:

public func after(when: DispatchTime, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @convention(block) () -> Void)

So to use it, you have to type:

DispatchQueue.main.after(when: DispatchTime.now() + .milliseconds(250)) { /* do stuff */ }

I don’t believe this is a great fit with the Swift API Guidelines. I believe the name “after” already implies that the time is relative, the argument label should be dropped, and that nuances about the clock (basically, whether or not it pauses during system sleep) are edge-cases which can be handled for the majority of users with a sensible default value (or better yet, modelling via an enum or boolean switch — see #2). Additionally, There are overloads with “wallTime” parameter labels which seem only to duplicate type information (the monotonic and walltime clocks are actually split at the type level) and could be more concise and readable. The principle is that, ultimately, you should just be able to write the above code like this:

DispatchQueue.main.after(.milliseconds(250)) { /* do stuff */ }

Or

DispatchQueue.main.at(DispatchTime.now() + .seconds(3)) { /* do stuff */ }

It’s not something you use all the time (like .sync/.async), but I find that whenever I do need it I’m frustrated by how needlessly complex it is to decipher and use. I would find these methods much more obvious, I could figure out how to use them much more quickly, and I think I’d remember how to use them more quickly.

As mentioned by Matt in the pull request you created, we’re working on a proposal that would look like this:

func asyncAfter(deadline:qos:flags:work:)
func asyncAfter(wallDeadline:qos:flags:work:)
As we discussed on the pull request,

DispatchQueue.main.after(.milliseconds(250)) { /* do stuff */ }

Is ambiguous as you don’t know which clock was meant, and despite your claim, there’s in general no good default for the clock. If you are writing a calendar app, you want wallClock, but if you’re doing anything network related, you will want the monotonic one. Depending on which software you’re writing, you will have a bias for one or the other.

Also it may be a native speaker thing (which I’m not) but this reads “please async this closure on `q` after this deadline has expired” to me, which sounds like proper english:

q.asyncAfter(deadline: .now() + 1.0) { /* do stuff */ }

I thought that you would say “please meet me in 10 minutes” or “please meet me after 2PM”. The `asyncBy` that you suggested to the pull request reads *before* that deadline to me which is not good either.
`asyncAt` would probably work too however, but asyncAfter is easier for people coming from C who are used to dispatch_after().

#2 -

Actually, while writing this down it’s becoming clearer and clearer that the idea to split DispatchTime (the monotonic clock) and DispatchWallTime (the wall clock) at the type level is probably a bad idea.

Novice users are not going to understand what’s going on here - I expect most of them to default to the more generic-sounding “DispatchTime” without any idea of the implications of this. Perhaps we should look at replacing these clock variations with a more descriptive enum or boolean, rather than a separate type. For example:

struct DispatchTime { // replaces DispatchTime and DispatchWallTime
   let rawValue : dispatch_time_t
   let clock : Clock

   enum Clock { case monotonicClock; case wallClock }
}

This would not obsolete the discussion at the start about “after”. The name “after” still implies that I want something done at some duration relative to an absolute point in time (usually now).

This is what dispatch_time_t does in C and my team at Apple widely considers this having been a design mistake: it means that the time is not generally Comparable, that you can’t perform any kind of arithmetic with it, etc.

In C it’s more convenient to have a single type, but in Swift where type is inferred, given that most of the uses will construct the time as the argument to the function itself, we feel that the current proposal allows for the most concise use.

-Pierre

···

On Jul 13, 2016, at 9:18 AM, Karl via swift-evolution <swift-evolution@swift.org> wrote:


(Karl) #4

nuances about the clock (basically, whether or not it pauses during system sleep) are edge-cases which can be handled for the majority of users with a sensible default value

I’m going to stay out of the naming part of the conversation for the moment and just address this point…

I disagree strenuously: it’s a super important distinction that is frequently the cause of subtle but bad bugs!

Lets start with the fact that you (presumably not the novice user you describe below) actually got the difference between the two clocks wrong. Here’s the current behavior (on Darwin, not sure what Linux currently implements):

  - DispatchTime is a clock that only advances forward and tracks the amount of time the computer is awake.

  - DispatchWillTime is a clock that tracks as a UTC clock would, generally moving ahead during sleep, but is *not* monotonic. It can move forward, backward, upside down, whatever it wants. And frequently it will.

Yeah I know, I don’t think I got the clocks wrong. I forgot that the UTC clock can move around, but yeah, of course it can; fair enough.

And this stuff is hard: if you are writing a tea timer app and you pick either of these, you have a bug. DispatchTime, the bug is that if the user locks their device and it falls asleep the alarm won’t fire upon wake. DispatchWallTime, if the device realizes its clock is ahead or behind the “real” time the elapsed duration won’t be what you expect. (Should we have a third that works for the tea timer? Absolutely…)

Do you think that is adequately expressed in the API?

I don’t, and I believe the current system of type-based overloads is not a good way to ensure people don’t accidentally use the wrong one. Maybe we should rename the methods which take each type to make it more of a conscious decision (I’m happy with that, because in that case we definitely have reasonable default values :sunglasses:)

So, what’s the sensible default you had in mind that won’t fail for a large portion of use cases? Safety is an important design point in an API surface and making these distinctions clear to developers is absolutely critical to achieving that goal.

The default clock depends on the context of what you’re doing. If I’m using DispatchQueue.after, I would say the monotonic clock is a reasonable default. Typically you’re going to be scheduling short fire-once things like performing an animation after a second or two (at the most). In that case, system sleep isn’t an issue - even on iOS where the user can lock the screen at any moment; your long-running alarm that crosses sleep events will still fire, it just won’t fire *immediately* upon wake. iOS in general makes a point not to offer guarantees about what will happen to your app if it ever goes in to the background, and offers alternative backgrounding and local notification APIs instead.

And again, if you considered it and really need that timer to fire immediately if the system dozed off for a little bit in-between, that’s available as an explicit consideration.

I could see how there’s an argument for a third type of timer; it’s obviously a complex topic, and we should provide a reasonable default if possible; even if that’s a platform-specific alias.

(Of course, Foundation.Timer gets all of the above horribly wrong, IMHO. We should fix that too…)

(the monotonic and walltime clocks are actually split at the type level)

DispatchTime is not a monotonic clock. At least, not in the POSIX sense. So let’s not call it that. (Linux’s failure to implement POSIX correctly notwithstanding.)

Novice users are not going to understand what’s going on here - I expect most of them to default to the more generic-sounding “DispatchTime” without any idea of the implications of this.

If that’s the case, that’s a good argument for forcing users to make the choice in an even more obvious and explicit way. Not making it easier to use one or the other when they’ll likely get it wrong.

Yes, that was the motivation behind saying we should merge them - while writing it, it feels like you’re on a bit of a tightrope - one slip and this code could mean something very different.

Karl

···

On 13 Jul 2016, at 18:47, Anthony Chivetta via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 13, 2016, at 9:18 , Karl via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Karl) #5

#1 -

Currently, DispatchQueue.after() takes an absolute time as the “after” parameter. This makes it hard to understand how to use it; you need to go digging through generated interfaces to find out what a ‘DispatchTime’ is and how you construct it (by getting the .now() and adding a DispatchTimeInterval using one of the easily-missable operator overloads, if you were wondering).

Here is what DispatchQueue.after looks like now:

public func after(when: DispatchTime, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @convention(block) () -> Void)

So to use it, you have to type:

DispatchQueue.main.after(when: DispatchTime.now() + .milliseconds(250)) { /* do stuff */ }

I don’t believe this is a great fit with the Swift API Guidelines. I believe the name “after” already implies that the time is relative, the argument label should be dropped, and that nuances about the clock (basically, whether or not it pauses during system sleep) are edge-cases which can be handled for the majority of users with a sensible default value (or better yet, modelling via an enum or boolean switch — see #2). Additionally, There are overloads with “wallTime” parameter labels which seem only to duplicate type information (the monotonic and walltime clocks are actually split at the type level) and could be more concise and readable. The principle is that, ultimately, you should just be able to write the above code like this:

DispatchQueue.main.after(.milliseconds(250)) { /* do stuff */ }

Or

DispatchQueue.main.at <http://dispatchqueue.main.at/>(DispatchTime.now() + .seconds(3)) { /* do stuff */ }

It’s not something you use all the time (like .sync/.async), but I find that whenever I do need it I’m frustrated by how needlessly complex it is to decipher and use. I would find these methods much more obvious, I could figure out how to use them much more quickly, and I think I’d remember how to use them more quickly.

As mentioned by Matt in the pull request you created, we’re working on a proposal that would look like this:

func asyncAfter(deadline:qos:flags:work:)
func asyncAfter(wallDeadline:qos:flags:work:)
As we discussed on the pull request,

DispatchQueue.main.after(.milliseconds(250)) { /* do stuff */ }

Is ambiguous as you don’t know which clock was meant, and despite your claim, there’s in general no good default for the clock. If you are writing a calendar app, you want wallClock, but if you’re doing anything network related, you will want the monotonic one. Depending on which software you’re writing, you will have a bias for one or the other.

Yeah I think we should split after(DispatchTime) and after(DispatchWallTime) to reflect their different ways of working.

after(DispatchTime) stays as it is, because it reflects an elapsed number of ticks
after(DispatchWallTime) should be renamed. I suggested it should be at(DispatchWallTime), and should only take an absolute value.

“after(deadline:)” isn’t better, I’m afraid. The definition is: "the latest time or date by which something should be completed.” We don’t really talk about “after” deadlines - deadlines are points in time you need to do something *before*. If something happens after a deadline, we say it “missed” the deadline. Like I said in the PR, that kind of timing guarantee is most clearly expressed by saying “by(deadline:)”.

Also it may be a native speaker thing (which I’m not) but this reads “please async this closure on `q` after this deadline has expired” to me, which sounds like proper english:

q.asyncAfter(deadline: .now() + 1.0) { /* do stuff */ }

I thought that you would say “please meet me in 10 minutes” or “please meet me after 2PM”. The `asyncBy` that you suggested to the pull request reads *before* that deadline to me which is not good either.
`asyncAt` would probably work too however, but asyncAfter is easier for people coming from C who are used to dispatch_after().

#2 -

Actually, while writing this down it’s becoming clearer and clearer that the idea to split DispatchTime (the monotonic clock) and DispatchWallTime (the wall clock) at the type level is probably a bad idea.

Novice users are not going to understand what’s going on here - I expect most of them to default to the more generic-sounding “DispatchTime” without any idea of the implications of this. Perhaps we should look at replacing these clock variations with a more descriptive enum or boolean, rather than a separate type. For example:

struct DispatchTime { // replaces DispatchTime and DispatchWallTime
   let rawValue : dispatch_time_t
   let clock : Clock

   enum Clock { case monotonicClock; case wallClock }
}

This would not obsolete the discussion at the start about “after”. The name “after” still implies that I want something done at some duration relative to an absolute point in time (usually now).

This is what dispatch_time_t does in C and my team at Apple widely considers this having been a design mistake: it means that the time is not generally Comparable, that you can’t perform any kind of arithmetic with it, etc.

In C it’s more convenient to have a single type, but in Swift where type is inferred, given that most of the uses will construct the time as the argument to the function itself, we feel that the current proposal allows for the most concise use.

OK, the motivation to unify them was because it’s quite easy to get them confused when one is named “after(when: DispatchTime, …” and the other is named “after(walltime when: DispatchWallTime, …”

Giving them more distinct function names would also work.

Karl

···

On 13 Jul 2016, at 18:56, Pierre Habouzit <phabouzit@apple.com> wrote:

On Jul 13, 2016, at 9:18 AM, Karl via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Daniel A. Steffen) #6

nuances about the clock (basically, whether or not it pauses during system sleep) are edge-cases which can be handled for the majority of users with a sensible default value

I’m going to stay out of the naming part of the conversation for the moment and just address this point…

I disagree strenuously: it’s a super important distinction that is frequently the cause of subtle but bad bugs!

Lets start with the fact that you (presumably not the novice user you describe below) actually got the difference between the two clocks wrong. Here’s the current behavior (on Darwin, not sure what Linux currently implements):

  - DispatchTime is a clock that only advances forward and tracks the amount of time the computer is awake.

  - DispatchWillTime is a clock that tracks as a UTC clock would, generally moving ahead during sleep, but is *not* monotonic. It can move forward, backward, upside down, whatever it wants. And frequently it will.

Yeah I know, I don’t think I got the clocks wrong. I forgot that the UTC clock can move around, but yeah, of course it can; fair enough.

And this stuff is hard: if you are writing a tea timer app and you pick either of these, you have a bug. DispatchTime, the bug is that if the user locks their device and it falls asleep the alarm won’t fire upon wake. DispatchWallTime, if the device realizes its clock is ahead or behind the “real” time the elapsed duration won’t be what you expect. (Should we have a third that works for the tea timer? Absolutely…)

Do you think that is adequately expressed in the API?

I don’t, and I believe the current system of type-based overloads is not a good way to ensure people don’t accidentally use the wrong one. Maybe we should rename the methods which take each type to make it more of a conscious decision (I’m happy with that, because in that case we definitely have reasonable default values :sunglasses:)

I’m confused, that is what we have in the current version (the argument label is part of the overall method name): asyncAfter(deadline:) vs asyncAfter(wallDeadline:), which is consistent with all the other labels of deadline arguments in the API (this isn’t the only method dealing with time)

we did discuss naming these asyncAt() instead of asyncAfter(), which would make it more clear that they take a deadline, but the name felt uncomfortably close to async(), and may also lead to the mistaken impression that the execution with occur exactly _at_ the deadline (as opposed to just the async() to a queue that may be full of other items already and take a while to drain, or be suspended and never execute at all)

So, what’s the sensible default you had in mind that won’t fail for a large portion of use cases? Safety is an important design point in an API surface and making these distinctions clear to developers is absolutely critical to achieving that goal.

The default clock depends on the context of what you’re doing. If I’m using DispatchQueue.after, I would say the monotonic clock is a reasonable default. Typically you’re going to be scheduling short fire-once things like performing an animation after a second or two (at the most). In that case, system sleep isn’t an issue - even on iOS where the user can lock the screen at any moment; your long-running alarm that crosses sleep events will still fire, it just won’t fire *immediately* upon wake.

Nothing in the API says it must be used only for "short fire-once things"

The problem with the monotonic clock isn’t about firing at wake, but about pushing the fire time out by the amount of time asleep.
If you are using this to implement e.g. a calendaring meeting reminder alarms, you are not going to be happy if your reminder is late by the amount of time that your device happened to put its cpu to sleep for many short intervals for power management reasons...

iOS in general makes a point not to offer guarantees about what will happen to your app if it ever goes in to the background, and offers alternative backgrounding and local notification APIs instead.

this API isn’t just designed for iOS Apps but for system programming in general, on any platform.

And again, if you considered it and really need that timer to fire immediately if the system dozed off for a little bit in-between, that’s available as an explicit consideration.

I could see how there’s an argument for a third type of timer; it’s obviously a complex topic, and we should provide a reasonable default if possible; even if that’s a platform-specific alias.

yes this was intentionally designed to accommodate additional clocks

···

On Jul 13, 2016, at 10:08, Karl via swift-evolution <swift-evolution@swift.org> wrote:

On 13 Jul 2016, at 18:47, Anthony Chivetta via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jul 13, 2016, at 9:18 , Karl via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

(Of course, Foundation.Timer gets all of the above horribly wrong, IMHO. We should fix that too…)

(the monotonic and walltime clocks are actually split at the type level)

DispatchTime is not a monotonic clock. At least, not in the POSIX sense. So let’s not call it that. (Linux’s failure to implement POSIX correctly notwithstanding.)

Novice users are not going to understand what’s going on here - I expect most of them to default to the more generic-sounding “DispatchTime” without any idea of the implications of this.

If that’s the case, that’s a good argument for forcing users to make the choice in an even more obvious and explicit way. Not making it easier to use one or the other when they’ll likely get it wrong.

Yes, that was the motivation behind saying we should merge them - while writing it, it feels like you’re on a bit of a tightrope - one slip and this code could mean something very different.

Karl

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Karl) #7

I’m confused, that is what we have in the current version (the argument label is part of the overall method name): asyncAfter(deadline:) vs asyncAfter(wallDeadline:), which is consistent with all the other labels of deadline arguments in the API (this isn’t the only method dealing with time)

I think this argument labels are superfluous and actually make the meaning less coherent. “after(when:…)” is not grammatically fluent, which the Swift API guidelines encourage, and which the standard library has made big steps towards recently (see especially https://github.com/apple/swift-evolution/blob/master/proposals/0118-closure-parameter-names-and-labels.md). By dropping the labels and renaming the function base-names, we make them more “swifty”, more concise, clear and readable, and can improve safety by making sure people know which clock they’re using and what semantic meaning that has.

we did discuss naming these asyncAt() instead of asyncAfter(), which would make it more clear that they take a deadline, but the name felt uncomfortably close to async(), and may also lead to the mistaken impression that the execution with occur exactly _at_ the deadline (as opposed to just the async() to a queue that may be full of other items already and take a while to drain, or be suspended and never execute at all)

I’m not sure it’s really necessary to include the word “async” in there — it’s pretty clear from the fact that they take a time that they’re not going to block.

The problem with “deadline” is that it’s just not a deadline. It’s an aspirational fire time, and Dispatch should execute the block as soon as possible after that time. I can’t really think of a concise word for it, but “deadline” does not express what you’re talking about. Deadline implies that the block can execute any time _before_ the specified time.

So that’s where I get “at” from; if your app is asleep, it isn’t possible to execute exactly at the specified time for reasons outside of your control. If it executes as soon as possible after waking, I would still consider it to be firing “at” the correct time (in a loose sort of way). If we were talking about the dispatch queue as a person, and I asked him/her to do something at a particular time, but they were delayed due to circumstances outside of anybody's control (like a natural disaster or a traffic accident), I’d still consider that they did it “at” the correct time, again in a loose sense - to the best that they can control it, in other words.

So, what’s the sensible default you had in mind that won’t fail for a large portion of use cases? Safety is an important design point in an API surface and making these distinctions clear to developers is absolutely critical to achieving that goal.

The default clock depends on the context of what you’re doing. If I’m using DispatchQueue.after, I would say the monotonic clock is a reasonable default. Typically you’re going to be scheduling short fire-once things like performing an animation after a second or two (at the most). In that case, system sleep isn’t an issue - even on iOS where the user can lock the screen at any moment; your long-running alarm that crosses sleep events will still fire, it just won’t fire *immediately* upon wake.

Nothing in the API says it must be used only for "short fire-once things"

The problem with the monotonic clock isn’t about firing at wake, but about pushing the fire time out by the amount of time asleep.
If you are using this to implement e.g. a calendaring meeting reminder alarms, you are not going to be happy if your reminder is late by the amount of time that your device happened to put its cpu to sleep for many short intervals for power management reasons…

No, nothing in the API does say that, but in this context I believe it’s the most commonly wanted thing and would be a reasonable default.

If you are implementing calendar reminders using dispatch_after in an application which can be suspended at any moment, you’re using the wrong API pure and simple. On Linux, you might be able to guarantee your app won’t be suspended so this strategy could work for you, but the API you use must be appropriate to the platform. If you can’t make that guarantee (e.g. On iOS), you should look for an alternative, such as the local notifications API, which is designed for exactly this.

iOS in general makes a point not to offer guarantees about what will happen to your app if it ever goes in to the background, and offers alternative backgrounding and local notification APIs instead.

this API isn’t just designed for iOS Apps but for system programming in general, on any platform.

No, but as above, the API you use must be appropriate for the platform. We shouldn’t worry about people on iOS using dispatch_after for calendar notifications. Those people will run in to all kinds of headaches anyway, asking them to be explicit about which clock they use is pretty fair IMO. I would say it might even help them...

···

On 13 Jul 2016, at 19:59, Daniel A. Steffen <das@apple.com> wrote:

And again, if you considered it and really need that timer to fire immediately if the system dozed off for a little bit in-between, that’s available as an explicit consideration.

I could see how there’s an argument for a third type of timer; it’s obviously a complex topic, and we should provide a reasonable default if possible; even if that’s a platform-specific alias.

yes this was intentionally designed to accommodate additional clocks

(Of course, Foundation.Timer gets all of the above horribly wrong, IMHO. We should fix that too…)

(the monotonic and walltime clocks are actually split at the type level)

DispatchTime is not a monotonic clock. At least, not in the POSIX sense. So let’s not call it that. (Linux’s failure to implement POSIX correctly notwithstanding.)

Novice users are not going to understand what’s going on here - I expect most of them to default to the more generic-sounding “DispatchTime” without any idea of the implications of this.

If that’s the case, that’s a good argument for forcing users to make the choice in an even more obvious and explicit way. Not making it easier to use one or the other when they’ll likely get it wrong.

Yes, that was the motivation behind saying we should merge them - while writing it, it feels like you’re on a bit of a tightrope - one slip and this code could mean something very different.

Karl

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Pierre Habouzit) #8

I’m confused, that is what we have in the current version (the argument label is part of the overall method name): asyncAfter(deadline:) vs asyncAfter(wallDeadline:), which is consistent with all the other labels of deadline arguments in the API (this isn’t the only method dealing with time)

I think this argument labels are superfluous and actually make the meaning less coherent. “after(when:…)” is not grammatically fluent, which the Swift API guidelines encourage, and which the standard library has made big steps towards recently (see especially https://github.com/apple/swift-evolution/blob/master/proposals/0118-closure-parameter-names-and-labels.md). By dropping the labels and renaming the function base-names, we make them more “swifty”, more concise, clear and readable, and can improve safety by making sure people know which clock they’re using and what semantic meaning that has.

after(when:) is the current state of the overlay but we told you we’re proposing something new, so your argument is not in complete good faith here is it? :wink:

we did discuss naming these asyncAt() instead of asyncAfter(), which would make it more clear that they take a deadline, but the name felt uncomfortably close to async(), and may also lead to the mistaken impression that the execution with occur exactly _at_ the deadline (as opposed to just the async() to a queue that may be full of other items already and take a while to drain, or be suspended and never execute at all)

I’m not sure it’s really necessary to include the word “async” in there — it’s pretty clear from the fact that they take a time that they’re not going to block.

The problem with “deadline” is that it’s just not a deadline. It’s an aspirational fire time, and Dispatch should execute the block as soon as possible after that time. I can’t really think of a concise word for it, but “deadline” does not express what you’re talking about. Deadline implies that the block can execute any time _before_ the specified time.

So that’s where I get “at” from; if your app is asleep, it isn’t possible to execute exactly at the specified time for reasons outside of your control. If it executes as soon as possible after waking, I would still consider it to be firing “at” the correct time (in a loose sort of way). If we were talking about the dispatch queue as a person, and I asked him/her to do something at a particular time, but they were delayed due to circumstances outside of anybody's control (like a natural disaster or a traffic accident), I’d still consider that they did it “at” the correct time, again in a loose sense - to the best that they can control it, in other words.

I strongly disagree that the two forms should be named differently, it’s even more confusing that if you use after() you get one clock and at() the other. Also when we will add a 3rd clock to dispatch, it just stops working completely (look at clock_gettime() that was finally added to macOS, it has 3 interesting clocks: MONOTONIC, UPTIME, and WALLTIME that posix calls REALTIME for some weird reason).

the functions should exactly differ with the argument tag to show that they basically perform the same task with a slight difference that is the clock you’re using. It’s concise, unambiguous, and regular with the other functions in the Dispatch module that handle time. Which in my opinion goes exactly in the direction of SE-0118.

So, what’s the sensible default you had in mind that won’t fail for a large portion of use cases? Safety is an important design point in an API surface and making these distinctions clear to developers is absolutely critical to achieving that goal.

The default clock depends on the context of what you’re doing. If I’m using DispatchQueue.after, I would say the monotonic clock is a reasonable default. Typically you’re going to be scheduling short fire-once things like performing an animation after a second or two (at the most). In that case, system sleep isn’t an issue - even on iOS where the user can lock the screen at any moment; your long-running alarm that crosses sleep events will still fire, it just won’t fire *immediately* upon wake.

Nothing in the API says it must be used only for "short fire-once things"

The problem with the monotonic clock isn’t about firing at wake, but about pushing the fire time out by the amount of time asleep.
If you are using this to implement e.g. a calendaring meeting reminder alarms, you are not going to be happy if your reminder is late by the amount of time that your device happened to put its cpu to sleep for many short intervals for power management reasons…

No, nothing in the API does say that, but in this context I believe it’s the most commonly wanted thing and would be a reasonable default.

If you are implementing calendar reminders using dispatch_after in an application which can be suspended at any moment, you’re using the wrong API pure and simple. On Linux, you might be able to guarantee your app won’t be suspended so this strategy could work for you, but the API you use must be appropriate to the platform. If you can’t make that guarantee (e.g. On iOS), you should look for an alternative, such as the local notifications API, which is designed for exactly this.

iOS in general makes a point not to offer guarantees about what will happen to your app if it ever goes in to the background, and offers alternative backgrounding and local notification APIs instead.

this API isn’t just designed for iOS Apps but for system programming in general, on any platform.

No, but as above, the API you use must be appropriate for the platform. We shouldn’t worry about people on iOS using dispatch_after for calendar notifications. Those people will run in to all kinds of headaches anyway, asking them to be explicit about which clock they use is pretty fair IMO. I would say it might even help them…

This was an example was to give you a sense of why what you’re asking feels wrong to us. But if you want a better one: dispatch_after() is completely suitable to have a notification in app when a given wall time passes if you’re in the app. It avoids the overhead and headaches of complex notification mechanisms, which are sometimes not even desired (if you’re not in the app, having the system wake up for that timer is a power issue). There are several apps doing that.

We hence don’t think that what you’re asking for is serving developers.

also note that the C API works that way for a long time:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, …), …)
dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, …), …)

to me it’s visually similar to:

q.asyncAfter(deadline: .now() + …) …
q.asyncAfter(wallDeadline: .now() + …) …

And while making things more swifty (and DispatchWorkItem shows that we’re dedicated to that when there’s a benefit), making it too dissimilar to the C interface for no good reason is something to IMO consider when picking names here.

-Pierre

···

On Jul 13, 2016, at 11:42 AM, Karl via swift-evolution <swift-evolution@swift.org> wrote:

On 13 Jul 2016, at 19:59, Daniel A. Steffen <das@apple.com <mailto:das@apple.com>> wrote:


(Karl) #9

I’m confused, that is what we have in the current version (the argument label is part of the overall method name): asyncAfter(deadline:) vs asyncAfter(wallDeadline:), which is consistent with all the other labels of deadline arguments in the API (this isn’t the only method dealing with time)

I think this argument labels are superfluous and actually make the meaning less coherent. “after(when:…)” is not grammatically fluent, which the Swift API guidelines encourage, and which the standard library has made big steps towards recently (see especially https://github.com/apple/swift-evolution/blob/master/proposals/0118-closure-parameter-names-and-labels.md). By dropping the labels and renaming the function base-names, we make them more “swifty”, more concise, clear and readable, and can improve safety by making sure people know which clock they’re using and what semantic meaning that has.

after(when:) is the current state of the overlay but we told you we’re proposing something new, so your argument is not in complete good faith here is it? :wink:

Yeah, I know - you’re proposing to rename it, I’m proposing that it should have a different type (which would be enabled by a different renaming to the one you’re proposing). So I think it’s a fair debate. I gave my opinion on “deadline" - the time you specify isn’t actually a deadline, is it? It isn’t the latest possible the time the block is allowed to execute; it’s the earliest possible time.

Maybe there is a language gap in there, maybe people use “deadline” more loosely to mean a general point in time in California, but the dictionary agrees that deadline means "the latest time or date by which something should be completed”, which is not what I think you mean (maybe I’m wrong, but that’s my understanding of what you’ve been saying so far).

we did discuss naming these asyncAt() instead of asyncAfter(), which would make it more clear that they take a deadline, but the name felt uncomfortably close to async(), and may also lead to the mistaken impression that the execution with occur exactly _at_ the deadline (as opposed to just the async() to a queue that may be full of other items already and take a while to drain, or be suspended and never execute at all)

I’m not sure it’s really necessary to include the word “async” in there — it’s pretty clear from the fact that they take a time that they’re not going to block.

The problem with “deadline” is that it’s just not a deadline. It’s an aspirational fire time, and Dispatch should execute the block as soon as possible after that time. I can’t really think of a concise word for it, but “deadline” does not express what you’re talking about. Deadline implies that the block can execute any time _before_ the specified time.

So that’s where I get “at” from; if your app is asleep, it isn’t possible to execute exactly at the specified time for reasons outside of your control. If it executes as soon as possible after waking, I would still consider it to be firing “at” the correct time (in a loose sort of way). If we were talking about the dispatch queue as a person, and I asked him/her to do something at a particular time, but they were delayed due to circumstances outside of anybody's control (like a natural disaster or a traffic accident), I’d still consider that they did it “at” the correct time, again in a loose sense - to the best that they can control it, in other words.

I strongly disagree that the two forms should be named differently, it’s even more confusing that if you use after() you get one clock and at() the other. Also when we will add a 3rd clock to dispatch, it just stops working completely (look at clock_gettime() that was finally added to macOS, it has 3 interesting clocks: MONOTONIC, UPTIME, and WALLTIME that posix calls REALTIME for some weird reason).

the functions should exactly differ with the argument tag to show that they basically perform the same task with a slight difference that is the clock you’re using. It’s concise, unambiguous, and regular with the other functions in the Dispatch module that handle time. Which in my opinion goes exactly in the direction of SE-0118.

Didn’t we agree earlier that the clock type is not a minor distinction? And that there are rather large differences about when the block might fire depending on which clock you use. This was Anthony Chievetta (maybe I misunderstood him, too):

I disagree strenuously: it’s a super important distinction that is frequently the cause of subtle but bad bugs!

Lets start with the fact that you (presumably not the novice user you describe below) actually got the difference between the two clocks wrong. Here’s the current behavior (on Darwin, not sure what Linux currently implements):

  - DispatchTime is a clock that only advances forward and tracks the amount of time the computer is awake.

  - DispatchWillTime is a clock that tracks as a UTC clock would, generally moving ahead during sleep, but is *not* monotonic. It can move forward, backward, upside down, whatever it wants. And frequently it will.

And this stuff is hard: if you are writing a tea timer app and you pick either of these, you have a bug. DispatchTime, the bug is that if the user locks their device and it falls asleep the alarm won’t fire upon wake. DispatchWallTime, if the device realizes its clock is ahead or behind the “real” time the elapsed duration won’t be what you expect. (Should we have a third that works for the tea timer? Absolutely…)

If there is some new clock introduced later, with different scheduling guarantees, it can get its own unique name to reflect what it does, right?

This was an example was to give you a sense of why what you’re asking feels wrong to us. But if you want a better one: dispatch_after() is completely suitable to have a notification in app when a given wall time passes if you’re in the app. It avoids the overhead and headaches of complex notification mechanisms, which are sometimes not even desired (if you’re not in the app, having the system wake up for that timer is a power issue). There are several apps doing that.

We hence don’t think that what you’re asking for is serving developers.

also note that the C API works that way for a long time:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, …), …)
dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, …), …)

to me it’s visually similar to:

q.asyncAfter(deadline: .now() + …) …
q.asyncAfter(wallDeadline: .now() + …) …

And while making things more swifty (and DispatchWorkItem shows that we’re dedicated to that when there’s a benefit), making it too dissimilar to the C interface for no good reason is something to IMO consider when picking names here.

The C API was also horrible in that regard. I bet there’s not a handful of people on this mailing list who could tell you exactly how to invoke dispatch_after. Everybody just uses the Xcode macro because it’s a mountain of jargon that almost nobody needs to care about.

That’s the whole point of all of this - I’m not proposing removing functionality, just making it more obvious and intuitive. If these clocks have such enormous differences, they should have unique names to reflect that — and since we now know the clock, we can supply defaults and take intervals rather than absolute times, making the names fluent and clear.

That’s why I think my proposed change is so much for the better - it leads to massive simplifications all-around, and things will just make so much more sense afterwards, without reducing functionality. Nuances that were hidden will be exposed, and you won’t need to dig through interfaces looking for operator overloads any more - autocomplete will be able to tell you exactly what to enter.

That’s why I’m not disparaged by how many of you Apple guys disagree - after all, you guys designed the original C API that this very-closely follows (which is a great API, don’t get me wrong, but this function hasn’t adapted to Swift yet IMO). It could be clearer and more approachable for end-users.

Karl

···

On 13 Jul 2016, at 21:04, Pierre Habouzit <phabouzit@apple.com> wrote:

On Jul 13, 2016, at 11:42 AM, Karl via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 13 Jul 2016, at 19:59, Daniel A. Steffen <das@apple.com <mailto:das@apple.com>> wrote:


(Ben Rimmington) #10

Methods taking DispatchTime or DispatchWallTime parameters have duplicate implementations. Shouldn't they be generic?

  public func wait<T: DispatchTime>(timeout: T? = nil) -> DispatchTimeoutResult

As I mentioned in the review, DISPATCH_TIME_FOREVER can be represented as an Optional parameter, so `distantFuture` isn't needed.

  group.wait(timeout: DispatchWallTime() + .seconds(10))

  group.wait()

DispatchTime would be a protocol, and the existing structure would be renamed.

  public protocol DispatchTime: Comparable, RawRepresentable {

      var rawValue: dispatch_time_t { get }

      init(rawValue: dispatch_time_t)

      init()
  }

-- Ben

···

On 13 Jul 2016, at 20:04, Pierre Habouzit via swift-evolution <swift-evolution@swift.org> wrote:

I strongly disagree that the two forms should be named differently, it’s even more confusing that if you use after() you get one clock and at() the other. Also when we will add a 3rd clock to dispatch, it just stops working completely (look at clock_gettime() that was finally added to macOS, it has 3 interesting clocks: MONOTONIC, UPTIME, and WALLTIME that posix calls REALTIME for some weird reason).

the functions should exactly differ with the argument tag to show that they basically perform the same task with a slight difference that is the clock you’re using. It’s concise, unambiguous, and regular with the other functions in the Dispatch module that handle time. Which in my opinion goes exactly in the direction of SE-0118.