Blame view

3rdparty/boost_1_81_0/libs/fiber/doc/callbacks.qbk 14.4 KB
977ed18d   Hu Chunming   提交三方库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
  [/
        Copyright Oliver Kowalke, Nat Goodspeed 2015.
   Distributed under the Boost Software License, Version 1.0.
      (See accompanying file LICENSE_1_0.txt or copy at
            http://www.boost.org/LICENSE_1_0.txt
  ]
  
  [/ import path is relative to this .qbk file]
  [import ../examples/adapt_callbacks.cpp]
  [import ../examples/adapt_method_calls.cpp]
  
  [#callbacks]
  [section:callbacks Integrating Fibers with Asynchronous Callbacks]
  
  [section Overview]
  
  One of the primary benefits of __boost_fiber__ is the ability to use
  asynchronous operations for efficiency, while at the same time structuring the
  calling code ['as if] the operations were synchronous. Asynchronous operations
  provide completion notification in a variety of ways, but most involve a
  callback function of some kind. This section discusses tactics for interfacing
  __boost_fiber__ with an arbitrary async operation.
  
  For purposes of illustration, consider the following hypothetical API:
  
  [AsyncAPI]
  
  The significant points about each of `init_write()` and `init_read()` are:
  
  * The `AsyncAPI` method only initiates the operation. It returns immediately,
    while the requested operation is still pending.
  * The method accepts a callback. When the operation completes, the callback is
    called with relevant parameters (error code, data if applicable).
  
  We would like to wrap these asynchronous methods in functions that appear
  synchronous by blocking the calling fiber until the operation completes. This
  lets us use the wrapper function[s] return value to deliver relevant data.
  
  [tip [template_link promise] and [template_link future] are your friends here.]
  
  [endsect]
  [section Return Errorcode]
  
  The `AsyncAPI::init_write()` callback passes only an `errorcode`. If we simply
  want the blocking wrapper to return that `errorcode`, this is an extremely
  straightforward use of [template_link promise] and [template_link future]:
  
  [callbacks_write_ec]
  
  All we have to do is:
  
  # Instantiate a `promise<>` of correct type.
  # Obtain its `future<>`.
  # Arrange for the callback to call [member_link promise..set_value].
  # Block on [member_link future..get].
  
  [note This tactic for resuming a pending fiber works even if the callback is
  called on a different thread than the one on which the initiating fiber is
  running. In fact, [@../../examples/adapt_callbacks.cpp the example program[s]]
  dummy `AsyncAPI` implementation illustrates that: it simulates async I/O by
  launching a new thread that sleeps briefly and then calls the relevant
  callback.]
  
  [endsect]
  [section Success or Exception]
  
  A wrapper more aligned with modern C++ practice would use an exception, rather
  than an `errorcode`, to communicate failure to its caller. This is
  straightforward to code in terms of `write_ec()`:
  
  [callbacks_write]
  
  The point is that since each fiber has its own stack, you need not repeat
  messy boilerplate: normal encapsulation works.
  
  [endsect]
  [section Return Errorcode or Data]
  
  Things get a bit more interesting when the async operation[s] callback passes
  multiple data items of interest. One approach would be to use `std::pair<>` to
  capture both:
  
  [callbacks_read_ec]
  
  Once you bundle the interesting data in `std::pair<>`, the code is effectively
  identical to `write_ec()`. You can call it like this:
  
  [callbacks_read_ec_call]
  
  [endsect]
  [#Data_or_Exception]
  [section Data or Exception]
  
  But a more natural API for a function that obtains data is to return only the
  data on success, throwing an exception on error.
  
  As with `write()` above, it[s] certainly possible to code a `read()` wrapper in
  terms of `read_ec()`. But since a given application is unlikely to need both,
  let[s] code `read()` from scratch, leveraging [member_link
  promise..set_exception]:
  
  [callbacks_read]
  
  [member_link future..get] will do the right thing, either returning
  `std::string` or throwing an exception.
  
  [endsect]
  [section Success/Error Virtual Methods]
  
  One classic approach to completion notification is to define an abstract base
  class with `success()` and `error()` methods. Code wishing to perform async
  I/O must derive a subclass, override each of these methods and pass the async
  operation a pointer to a subclass instance. The abstract base class might look
  like this:
  
  [Response]
  
  Now the `AsyncAPI` operation might look more like this:
  
  [method_init_read]
  
  We can address this by writing a one-size-fits-all `PromiseResponse`:
  
  [PromiseResponse]
  
  Now we can simply obtain the `future<>` from that `PromiseResponse` and wait
  on its `get()`:
  
  [method_read]
  
  [/ @path link is relative to (eventual) doc/html/index.html, hence ../..]
  The source code above is found in
  [@../../examples/adapt_callbacks.cpp adapt_callbacks.cpp]
  and
  [@../../examples/adapt_method_calls.cpp adapt_method_calls.cpp].
  
  [endsect]
  [#callbacks_asio]
  [section Then There[s] __boost_asio__]
  
  [import ../examples/asio/yield.hpp]
  [import ../examples/asio/detail/yield.hpp]
  
  Since the simplest form of Boost.Asio asynchronous operation completion token
  is a callback function, we could apply the same tactics for Asio as for our
  hypothetical `AsyncAPI` asynchronous operations.
  
  Fortunately we need not. Boost.Asio incorporates a mechanism[footnote This
  mechanism has been proposed as a conventional way to allow the caller of an
  arbitrary async function to specify completion handling:
  [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4045.pdf N4045].]
  by which the caller can customize the notification behavior of any async
  operation. Therefore we can construct a ['completion token] which, when passed
  to a __boost_asio__ async operation, requests blocking for the calling fiber.
  
  A typical Asio async function might look something like this:[footnote per [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4045.pdf N4045]]
  
      template < ..., class CompletionToken >
      ``['deduced_return_type]``
      async_something( ... , CompletionToken&& token)
      {
          // construct handler_type instance from CompletionToken
          handler_type<CompletionToken, ...>::type ``[*[`handler(token)]]``;
          // construct async_result instance from handler_type
          async_result<decltype(handler)> ``[*[`result(handler)]]``;
  
          // ... arrange to call handler on completion ...
          // ... initiate actual I/O operation ...
  
          return ``[*[`result.get()]]``;
      }
  
  We will engage that mechanism, which is based on specializing Asio[s]
  `handler_type<>` template for the `CompletionToken` type and the signature of
  the specific callback. The remainder of this discussion will refer back to
  `async_something()` as the Asio async function under consideration.
  
  The implementation described below uses lower-level facilities than `promise`
  and `future` because the `promise` mechanism interacts badly with
  [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/io_service/stop.html
  `io_service::stop()`]. It produces `broken_promise` exceptions.
  
  `boost::fibers::asio::yield` is a completion token of this kind. `yield` is an
  instance of `yield_t`:
  
  [fibers_asio_yield_t]
  
  `yield_t` is in fact only a placeholder, a way to trigger Boost.Asio
  customization. It can bind a
  [@http://www.boost.org/doc/libs/release/libs/system/doc/reference.html#Class-error_code
  `boost::system::error_code`]
  for use by the actual handler.
  
  `yield` is declared as:
  
  [fibers_asio_yield]
  
  Asio customization is engaged by specializing
  [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/handler_type.html
  `boost::asio::handler_type<>`]
  for `yield_t`:
  
  [asio_handler_type]
  
  (There are actually four different specializations in
  [@../../examples/asio/detail/yield.hpp detail/yield.hpp],
  one for each of the four Asio async callback signatures we expect.)
  
  The above directs Asio to use `yield_handler` as the actual handler for an
  async operation to which `yield` is passed. There[s] a generic
  `yield_handler<T>` implementation and a `yield_handler<void>` specialization.
  Let[s] start with the `<void>` specialization:
  
  [fibers_asio_yield_handler_void]
  
  `async_something()`, having consulted the `handler_type<>` traits
  specialization, instantiates a `yield_handler<void>` to be passed as the
  actual callback for the async operation. `yield_handler`[s] constructor accepts
  the `yield_t` instance (the `yield` object passed to the async function) and
  passes it along to `yield_handler_base`:
  
  [fibers_asio_yield_handler_base]
  
  `yield_handler_base` stores a copy of the `yield_t` instance [mdash] which, as
  shown above, contains only an `error_code*`. It also captures the
  [class_link context]* for the currently-running fiber by calling [member_link
  context..active].
  
  You will notice that `yield_handler_base` has one more data member (`ycomp_`)
  that is initialized to `nullptr` by its constructor [mdash] though its
  `operator()()` method relies on `ycomp_` being non-null. More on this in a
  moment.
  
  Having constructed the `yield_handler<void>` instance, `async_something()`
  goes on to construct an `async_result` specialized for the
  `handler_type<>::type`: in this case, `async_result<yield_handler<void>>`. It
  passes the `yield_handler<void>` instance to the new `async_result` instance.
  
  [fibers_asio_async_result_void]
  
  Naturally that leads us straight to `async_result_base`:
  
  [fibers_asio_async_result_base]
  
  This is how `yield_handler_base::ycomp_` becomes non-null:
  `async_result_base`[s] constructor injects a pointer back to its own
  `yield_completion` member.
  
  Recall that the canonical `yield_t` instance `yield` initializes its
  `error_code*` member `ec_` to `nullptr`. If this instance is passed to
  `async_something()` (`ec_` is still `nullptr`), the copy stored in
  `yield_handler_base` will likewise have null `ec_`. `async_result_base`[s]
  constructor sets `yield_handler_base`[s] `yield_t`[s] `ec_` member to point to
  its own `error_code` member.
  
  The stage is now set. `async_something()` initiates the actual async
  operation, arranging to call its `yield_handler<void>` instance on completion.
  Let[s] say, for the sake of argument, that the actual async operation[s]
  callback has signature `void(error_code)`.
  
  But since it[s] an async operation, control returns at once to
  `async_something()`. `async_something()` calls
  `async_result<yield_handler<void>>::get()`, and will return its return value.
  
  `async_result<yield_handler<void>>::get()` inherits
  `async_result_base::get()`.
  
  `async_result_base::get()` immediately calls `yield_completion::wait()`.
  
  [fibers_asio_yield_completion]
  
  Supposing that the pending async operation has not yet completed,
  `yield_completion::completed_` will still be `false`, and `wait()` will call
  [member_link context..suspend] on the currently-running fiber.
  
  Other fibers will now have a chance to run.
  
  Some time later, the async operation completes. It calls
  `yield_handler<void>::operator()(error_code const&)` with an `error_code` indicating
  either success or failure. We[,]ll consider both cases.
  
  `yield_handler<void>` explicitly inherits `operator()(error_code const&)` from
  `yield_handler_base`.
  
  `yield_handler_base::operator()(error_code const&)` first sets
  `yield_completion::completed_` `true`. This way, if `async_something()`[s]
  async operation completes immediately [mdash] if
  `yield_handler_base::operator()` is called even before
  `async_result_base::get()` [mdash] the calling fiber will ['not] suspend.
  
  The actual `error_code` produced by the async operation is then stored through
  the stored `yield_t::ec_` pointer. If `async_something()`[s] caller used (e.g.)
  `yield[my_ec]` to bind a local `error_code` instance, the actual `error_code`
  value is stored into the caller[s] variable. Otherwise, it is stored into
  `async_result_base::ec_`.
  
  If the stored fiber context `yield_handler_base::ctx_` is not already running,
  it is marked as ready to run by passing it to [member_link
  context..schedule]. Control then returns from
  `yield_handler_base::operator()`: the callback is done.
  
  In due course, that fiber is resumed. Control returns from [member_link
  context..suspend] to `yield_completion::wait()`, which returns to
  `async_result_base::get()`.
  
  * If the original caller passed `yield[my_ec]` to `async_something()` to bind
    a local `error_code` instance, then `yield_handler_base::operator()` stored
    its `error_code` to the caller[s] `my_ec` instance, leaving
    `async_result_base::ec_` initialized to success.
  * If the original caller passed `yield` to `async_something()` without binding
    a local `error_code` variable, then `yield_handler_base::operator()` stored
    its `error_code` into `async_result_base::ec_`. If in fact that `error_code`
    is success, then all is well.
  * Otherwise [mdash] the original caller did not bind a local `error_code` and
    `yield_handler_base::operator()` was called with an `error_code` indicating
    error [mdash] `async_result_base::get()` throws `system_error` with that
    `error_code`.
  
  The case in which `async_something()`[s] completion callback has signature
  `void()` is similar. `yield_handler<void>::operator()()` invokes the machinery
  above with a ["success] `error_code`.
  
  A completion callback with signature `void(error_code, T)` (that is: in
  addition to `error_code`, callback receives some data item) is handled
  somewhat differently. For this kind of signature, `handler_type<>::type`
  specifies `yield_handler<T>` (for `T` other than `void`).
  
  A `yield_handler<T>` reserves a `value_` pointer to a value of type `T`:
  
  [fibers_asio_yield_handler_T]
  
  This pointer is initialized to `nullptr`.
  
  When `async_something()` instantiates `async_result<yield_handler<T>>`:
  
  [fibers_asio_async_result_T]
  
  this `async_result<>` specialization reserves a member of type `T` to receive
  the passed data item, and sets `yield_handler<T>::value_` to point to its own
  data member.
  
  `async_result<yield_handler<T>>` overrides `get()`. The override calls
  `async_result_base::get()`, so the calling fiber suspends as described above.
  
  `yield_handler<T>::operator()(error_code, T)` stores its passed `T` value into
  `async_result<yield_handler<T>>::value_`.
  
  Then it passes control to `yield_handler_base::operator()(error_code)` to deal
  with waking the original fiber as described above.
  
  When `async_result<yield_handler<T>>::get()` resumes, it returns the stored
  `value_` to `async_something()` and ultimately to `async_something()`[s]
  caller.
  
  The case of a callback signature `void(T)` is handled by having
  `yield_handler<T>::operator()(T)` engage the `void(error_code, T)` machinery,
  passing a ["success] `error_code`.
  
  [/ @path link is relative to (eventual) doc/html/index.html, hence ../..]
  The source code above is found in
  [@../../examples/asio/yield.hpp yield.hpp] and
  [@../../examples/asio/detail/yield.hpp detail/yield.hpp].
  
  [endsect]
  [endsect]