OLD | NEW |
(Empty) | |
| 1 # Threading |
| 2 |
| 3 [TOC] |
| 4 |
| 5 ## Overview |
| 6 |
| 7 Chromium is a very multithreaded product. We try to keep the UI as responsive as |
| 8 possible, and this means not blocking the UI thread with any blocking I/O or |
| 9 other expensive operations. Our approach is to use message passing as the way of |
| 10 communicating between threads. We discourage locking and threadsafe |
| 11 objects. Instead, objects live on only one thread, we pass messages between |
| 12 threads for communication, and we use callback interfaces (implemented by |
| 13 message passing) for most cross-thread requests. |
| 14 |
| 15 The `Thread` object is defined in |
| 16 [`base/threading/thread.h`](https://cs.chromium.org/chromium/src/base/threading/
thread.h). |
| 17 In general you should probably use one of the existing threads described below |
| 18 rather than make new ones. We already have a lot of threads that are difficult |
| 19 to keep track of. Each thread has a `MessageLoop` (see |
| 20 [`base/message_loop/message_loop.h`](https://cs.chromium.org/chromium/src/base/m
essage_loop/message_loop.h) |
| 21 that processes messages for that thread. You can get the message loop for a |
| 22 thread using the `Thread.message_loop()` function. More details about |
| 23 `MessageLoop` can be found in |
| 24 [Anatomy of Chromium MessageLoop](https://docs.google.com/document/d/1_pJUHO3f3V
yRSQjEhKVvUU7NzCyuTCQshZvbWeQiCXU/view#). |
| 25 |
| 26 ## Existing threads |
| 27 |
| 28 Most threads are managed by the BrowserProcess object, which acts as the service |
| 29 manager for the main "browser" process. By default, everything happens on the UI |
| 30 thread. We have pushed certain classes of processing into these other |
| 31 threads. It has getters for the following threads: |
| 32 |
| 33 * **ui_thread**: Main thread where the application starts up. |
| 34 * **io_thread**: This thread is somewhat mis-named. It is the dispatcher thread |
| 35 that handles communication between the browser process and all the |
| 36 sub-processes. It is also where all resource requests (web page loads) are |
| 37 dispatched from (see |
| 38 [Multi-process Architecture](https://www.chromium.org/developers/design-docum
ents/multi-process-architecture)). |
| 39 * **file_thread**: A general process thread for file operations. When you want
to |
| 40 do blocking filesystem operations (for example, requesting an icon for a file |
| 41 type, or writing downloaded files to disk), dispatch to this thread. |
| 42 * **db_thread**: A thread for database operations. For example, the cookie |
| 43 service does sqlite operations on this thread. Note that the history database |
| 44 doesn't use this thread yet. |
| 45 * **safe_browsing_thread** |
| 46 |
| 47 Several components have their own threads: |
| 48 |
| 49 * **History**: The history service object has its own thread. This might be |
| 50 merged with the db_thread above. However, we need to be sure that things |
| 51 happen in the correct order -- for example, that cookies are loaded before |
| 52 history since cookies are needed for the first load, and history |
| 53 initialization is long and will block it. |
| 54 * **Proxy service**: See |
| 55 [`net/http/http_proxy_service.cc`](https://cs.chromium.org/chromium/src/net/h
ttp/http_proxy_service.cc). |
| 56 * **Automation proxy**: This thread is used to communicate with the UI test |
| 57 program driving the app. |
| 58 |
| 59 ## Keeping the browser responsive |
| 60 |
| 61 As hinted in the overview, we avoid doing any blocking I/O on the UI thread to |
| 62 keep the UI responsive. Less apparent is that we also need to avoid blocking |
| 63 I/O on the IO thread. The reason is that if we block it for an expensive |
| 64 operation, say disk access, then IPC messages don't get processed. The effect |
| 65 is that the user can't interact with a page. Note that asynchronous/overlapped |
| 66 I/O are fine. |
| 67 |
| 68 Another thing to watch out for is to not block threads on one another. Locks |
| 69 should only be used to swap in a shared data structure that can be accessed on |
| 70 multiple threads. If one thread updates it based on expensive computation or |
| 71 through disk access, then that slow work should be done without holding on to |
| 72 the lock. Only when the result is available should the lock be used to swap in |
| 73 the new data. An example of this is in PluginList::LoadPlugins |
| 74 ([`content/common/plugin_list.cc`](https://cs.chromium.org/chromium/src/content/
common/plugin_list.cc). If |
| 75 you must use locks, |
| 76 [here](https://www.chromium.org/developers/lock-and-condition-variable) |
| 77 are some best practices and pitfalls to avoid. |
| 78 |
| 79 In order to write non-blocking code, many APIs in Chromium are |
| 80 asynchronous. Usually this means that they either need to be executed on a |
| 81 particular thread and will return results via a custom delegate interface, or |
| 82 they take a `base::Callback<>` object that is called when the requested |
| 83 operation is completed. Executing work on a specific thread is covered in the |
| 84 PostTask section below. |
| 85 |
| 86 ## Getting stuff to other threads |
| 87 |
| 88 ### `base::Callback<>`, Async APIs and Currying |
| 89 |
| 90 |
| 91 A `base::Callback<>` (see the docs in |
| 92 [`base/callback.h`](https://cs.chromium.org/chromium/src/base/callback.h) is |
| 93 a templated class with a `Run()` method. It is a generalization of a function |
| 94 pointer and is created by a call to `base::Bind`. Async APIs often will take a |
| 95 `base::Callback<>` as a means to asynchronously return the results of an |
| 96 operation. Here is an example of a hypothetical FileRead API. |
| 97 |
| 98 void ReadToString(const std::string& filename, const base::Callback<void(con
st std::string&)>& on_read); |
| 99 |
| 100 void DisplayString(const std::string& result) { |
| 101 LOG(INFO) << result; |
| 102 } |
| 103 |
| 104 void SomeFunc(const std::string& file) { |
| 105 ReadToString(file, base::Bind(&DisplayString)); |
| 106 }; |
| 107 |
| 108 In the example above, `base::Bind` takes the function pointer `&DisplayString` |
| 109 and turns it into a `base::Callback<void(const std::string& result)>`. The type |
| 110 of the generated `base::Callback<>` is inferred from the arguments. Why not |
| 111 just pass the function pointer directly? The reason is `base::Bind` allows the |
| 112 caller to adapt function interfaces and/or attach extra context |
| 113 via [Currying](http://en.wikipedia.org/wiki/Currying). For instance, if we had |
| 114 a utility function `DisplayStringWithPrefix` that took an extra argument with |
| 115 the prefix, we use `base::Bind` to adapt the interface as follows. |
| 116 |
| 117 void DisplayStringWithPrefix(const std::string& prefix, const std::string& r
esult) { |
| 118 LOG(INFO) << prefix << result; |
| 119 } |
| 120 |
| 121 void AnotherFunc(const std::string& file) { |
| 122 ReadToString(file, base::Bind(&DisplayStringWithPrefix, "MyPrefix: ")); |
| 123 }; |
| 124 |
| 125 This can be used in lieu of creating an adapter functions a small classes that |
| 126 holds prefix as a member variable. Notice also that the `"MyPrefix: "` argument |
| 127 is actually a `const char*`, while `DisplayStringWithPrefix` actually wants a |
| 128 `const std::string&`. Like normal function dispatch, `base::Bind`, will coerce |
| 129 parameters types if possible. |
| 130 |
| 131 See [How arguments are handled by base::Bind()](#how_arguments_are_handled) |
| 132 below for more details about argument storage, copying, and special handling of |
| 133 references. |
| 134 |
| 135 ### PostTask |
| 136 |
| 137 The lowest level of dispatching to another thread is to use the |
| 138 `MessageLoop.PostTask` and `MessageLoop.PostDelayedTask` |
| 139 (see |
| 140 [`base/message_loop/message_loop.h`](https://cs.chromium.org/chromium/src/base/m
essage_loop/message_loop.h)). |
| 141 PostTask schedules a task to be run on a particular thread. A task is defined |
| 142 as a `base::Closure`, which is a typedef for a |
| 143 `base::Callback<void(void)>`. `PostDelayedTask` schedules a task to be run after |
| 144 a delay on a particular thread. A task is represented by the `base::Closure` |
| 145 typedef, which contains a `Run()` function, and is created by calling |
| 146 `base::Bind()`. To process a task, the message loop eventually calls |
| 147 `base::Closure`'s `Run` function, and then drops the reference to the task |
| 148 object. Both `PostTask` and `PostDelayedTask` take a `tracked_objects::Location` |
| 149 parameter, which is used for lightweight debugging purposes (counts and |
| 150 primitive profiling of pending and completed tasks can be monitored in a debug |
| 151 build via the url about:objects). Generally the macro value `FROM_HERE` is the |
| 152 appropriate value to use in this parameter. |
| 153 |
| 154 Note that new tasks go on the message loop's queue, and any delay that is |
| 155 specified is subject to the operating system's timer resolutions. This means |
| 156 that under Windows, very small timeouts (under 10ms) will likely not be honored |
| 157 (and will be longer). Using a timeout of 0 in `PostDelayedTask` is equivalent to |
| 158 calling `PostTask`, and adds no delay beyond queuing delay. `PostTask` is also |
| 159 used to do something on the current thread "sometime after the current |
| 160 processing returns to the message loop." Such a continuation on the current |
| 161 thread can be used to assure that other time critical tasks are not starved on |
| 162 this thread. |
| 163 |
| 164 The following is an example of a creating a task for a function and posting it |
| 165 to another thread (in this example, the file thread): |
| 166 |
| 167 void WriteToFile(const std::string& filename, const std::string& data); |
| 168 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| 169 base::Bind(&WriteToFile, "foo.txt", "hello world!"))
; |
| 170 |
| 171 You should always use `BrowserThread` to post tasks between threads. Never |
| 172 cache `MessageLoop` pointers as it can cause bugs such as the pointers being |
| 173 deleted while you're still holding on to them. More information can be |
| 174 found |
| 175 [here](https://www.chromium.org/developers/design-documents/threading/suble-thre
ading-bugs-and-patterns-to-avoid-them). |
| 176 |
| 177 |
| 178 ### base::Bind() and class methods. |
| 179 |
| 180 The `base::Bind()` API also supports invoking class methods as well. The syntax |
| 181 is very similar to calling `base::Bind()` on a function, except the first |
| 182 argument should be the object the method belongs to. By default, the object that |
| 183 `PostTask` uses must be a thread-safe reference-counted object. Reference |
| 184 counting ensures that the object invoked on another thread will stay alive until |
| 185 the task completes. |
| 186 |
| 187 class MyObject : public base::RefCountedThreadSafe<MyObject> { |
| 188 public: |
| 189 void DoSomething(const std::string16& name) { |
| 190 thread_->message_loop()->PostTask( |
| 191 FROM_HERE, base::Bind(&MyObject::DoSomethingOnAnotherThread, this, na
me)); |
| 192 } |
| 193 |
| 194 void DoSomethingOnAnotherThread(const std::string16& name) { |
| 195 ... |
| 196 } |
| 197 private: |
| 198 // Always good form to make the destructor private so that only RefCounted |
| 199 // ThreadSafe can access it. |
| 200 // This avoids bugs with double deletes. |
| 201 friend class base::RefCountedThreadSafe<MyObject>; |
| 202 |
| 203 ~MyObject(); |
| 204 Thread* thread_; |
| 205 }; |
| 206 |
| 207 If you have external synchronization structures that can completely ensure that |
| 208 an object will always be alive while the task is waiting to execute, you can |
| 209 wrap the object pointer with `base::Unretained()` when calling `base::Bind()` to |
| 210 disable the refcounting. This will also allow using `base::Bind()` on classes |
| 211 that are not refcounted. Be careful when doing this! |
| 212 |
| 213 |
| 214 |
| 215 ### How arguments are handled by `base::Bind()` |
| 216 <a id="how_arguments_are_handled"></a> |
| 217 |
| 218 The arguments given to `base::Bind()` are copied into an internal |
| 219 `InvokerStorage` structure object (defined in |
| 220 [`base/bind_internal.h`](http://cs.chromium.org/chromium/src/base/bind_internal.
h). |
| 221 When the function is finally executed, it will see copies of the arguments. Thi
s is important if your target function or method takes a const reference; the |
| 222 reference will be to a copy of the argument. If you need a reference to the |
| 223 original argument, you can wrap the argument with `base::ConstRef()`. Use this |
| 224 carefully as it is likely dangerous if target of the reference cannot be |
| 225 guaranteed to live past when the task is executed. In particular, it is almost |
| 226 never safe to use `base::ConstRef()` to a variable on the stack unless you can |
| 227 guarantee the stack frame will not be invalidated until the asynchronous task |
| 228 finishes. |
| 229 |
| 230 Sometimes, you will want to pass reference-counted objects as parameters (be |
| 231 sure to use `RefCountedThreadSafe` and not plain `RefCounted` as the base class |
| 232 for these objects). To ensure that the object lives throughout the entire |
| 233 request, the Closure generated by `base::Bind` must keep a reference to it. This |
| 234 can be done by passing scoped_refptr as the parameter type, or by wrapping the |
| 235 raw pointer with `make_scoped_refptr()`: |
| 236 |
| 237 class SomeParamObject : public base::RefCountedThreadSafe<SomeParamObject> { |
| 238 ... |
| 239 }; |
| 240 |
| 241 class MyObject : public base::RefCountedThreadSafe<MyObject> { |
| 242 public: |
| 243 void DoSomething() { |
| 244 scoped_refptr<SomeParamObject> param(new SomeParamObject); |
| 245 thread_->message_loop()->PostTask(FROM_HERE |
| 246 base::Bind(&MyObject::DoSomethingOnAnotherThread, this, param)); |
| 247 } |
| 248 void DoSomething2() { |
| 249 SomeParamObject* param = new SomeParamObject; |
| 250 thread_->message_loop()->PostTask(FROM_HERE |
| 251 base::Bind(&MyObject::DoSomethingOnAnotherThread, this, |
| 252 make_scoped_refptr(param))); |
| 253 } |
| 254 // Note how this takes a raw pointer. The important part is that |
| 255 // base::Bind() was passed a scoped_refptr; using a scoped_refptr |
| 256 // here would result in an extra AddRef()/Release() pair. |
| 257 void DoSomethingOnAnotherThread(SomeParamObject* param) { |
| 258 ... |
| 259 } |
| 260 }; |
| 261 |
| 262 If you want to pass the object without taking a reference on it, wrap the |
| 263 argument with `base::Unretained()`. Again, using this means there are external |
| 264 guarantees on the lifetime of the object, so tread carefully! |
| 265 |
| 266 If your object has a non-trivial destructor that needs to run on a specific |
| 267 thread, you can use the following trait. This is needed since timing races could |
| 268 lead to a task completing execution before the code that posted it has unwound |
| 269 the stack. |
| 270 |
| 271 class MyObject : public base::RefCountedThreadSafe<MyObject, BrowserThread::
DeleteOnIOThread> { |
| 272 |
| 273 ## Callback cancellation |
| 274 |
| 275 There are 2 major reasons to cancel a task (in the form of a Callback): |
| 276 * You want to do something later on your object, but at the time your callback |
| 277 runs, your object may have been destroyed. |
| 278 * When input changes (e.g. user input), old tasks become unnecessary. For |
| 279 performance considerations, you should cancel them. |
| 280 See following about different approaches for cancellation. |
| 281 |
| 282 ### Important notes about cancellation |
| 283 |
| 284 It's dangerous to cancel a task with owned parameters. See the following |
| 285 example. (The example uses `base::WeakPtr` for cancellation, but the problem |
| 286 applies to all approaches). |
| 287 |
| 288 class MyClass { |
| 289 public: |
| 290 // Owns |p|. |
| 291 void DoSomething(AnotherClass* p) { |
| 292 ... |
| 293 } |
| 294 WeakPtr<MyClass> AsWeakPtr() { |
| 295 return weak_factory_.GetWeakPtr(); |
| 296 } |
| 297 private: |
| 298 base::WeakPtrFactory<MyClass> weak_factory_; |
| 299 }; |
| 300 |
| 301 ... |
| 302 Closure cancelable_closure = Bind(&MyClass::DoSomething, object->AsWeakPtr()
, p); |
| 303 Callback<void(AnotherClass*)> cancelable_callback = Bind(&MyClass::DoSomethi
ng, object->AsWeakPtr()); |
| 304 ... |
| 305 |
| 306 void FunctionRunLater(const Closure& cancelable_closure, |
| 307 const Callback<void(AnotherClass*)>& cancelable_callba
ck) { |
| 308 ... |
| 309 // Leak memory! |
| 310 cancelable_closure.Run(); |
| 311 cancelable_callback.Run(p); |
| 312 } |
| 313 |
| 314 In `FunctionRunLater`, both `Run()` calls will leak `p` when object is already |
| 315 destructed. Using `scoped_ptr` can fix the bug: |
| 316 |
| 317 class MyClass { |
| 318 public: |
| 319 void DoSomething(scoped_ptr<AnotherClass> p) { |
| 320 ... |
| 321 } |
| 322 ... |
| 323 }; |
| 324 |
| 325 ### base::WeakPtr and Cancellation __[NOT THREAD SAFE]__ |
| 326 |
| 327 You can use a `base::WeakPtr` and `base::WeakPtrFactory` |
| 328 (in |
| 329 [base/memory/weak_ptr.h](https://cs.chromium.org/chromium/src/base/memory/weak_p
tr.h)) |
| 330 to ensure that any invokes can not outlive the object they are being invoked on, |
| 331 without using reference counting. The `base::Bind` mechanism has special |
| 332 understanding for `base::WeakPtr` that will disable the task's execution if the |
| 333 `base::WeakPtr` has been invalidated. The `base::WeakPtrFactory` object can be |
| 334 used to generate `base::WeakPtr` instances that know about the factory |
| 335 object. When the factory is destroyed, all the `base::WeakPtr` will have their |
| 336 internal "invalidated" flag set, which will make any tasks bound to them to not |
| 337 dispatch. By putting the factory as a member of the object being dispatched to, |
| 338 you can get automatic cancellation. |
| 339 |
| 340 __NOTE__: This only works when the task is posted to the same thread. Currently |
| 341 there is not a general solution that works for tasks posted to other |
| 342 threads. See |
| 343 the [next section about CancelableTaskTracker](#cancelable_task_tracker) for an |
| 344 alternative solution. |
| 345 |
| 346 class MyObject { |
| 347 public: |
| 348 MyObject() : weak_factory_(this) {} |
| 349 |
| 350 void DoSomething() { |
| 351 const int kDelayMS = 100; |
| 352 MessageLoop::current()->PostDelayedTask(FROM_HERE, |
| 353 base::Bind(&MyObject::DoSomethingLater, weak_factory_.GetWeakPtr()), |
| 354 kDelayMS); |
| 355 } |
| 356 |
| 357 void DoSomethingLater() { |
| 358 ... |
| 359 } |
| 360 |
| 361 private: |
| 362 base::WeakPtrFactory<MyObject> weak_factory_; |
| 363 }; |
| 364 |
| 365 ### CancelableTaskTracker |
| 366 <a id="cancelable_task_tracker"></a> |
| 367 |
| 368 While `base::WeakPtr` is very helpful to cancel a task, it is not thread safe so |
| 369 can not be used to cancel tasks running on another thread. This is sometimes a |
| 370 performance critical requirement. E.g. We need to cancel database lookup task on |
| 371 DB thread when user changes inputed text. In this kind of situation |
| 372 `CancelableTaskTracker` is appropriate. |
| 373 |
| 374 With `CancelableTaskTracker` you can cancel a single task with returned |
| 375 `TaskId`. This is another reason to use `CancelableTaskTracker` instead of |
| 376 `base::WeakPtr`, even in a single thread context. |
| 377 |
| 378 `CancelableTaskTracker` has 2 `Post` methods doing the same thing as the ones in |
| 379 `base::TaskRunner`, with additional cancellation support. |
| 380 |
| 381 class UserInputHandler : public base::RefCountedThreadSafe<UserInputHandler>
{ |
| 382 // Runs on UI thread. |
| 383 void OnUserInput(Input input) { |
| 384 CancelPreviousTask(); |
| 385 DBResult* result = new DBResult(); |
| 386 task_id_ = tracker_->PostTaskAndReply( |
| 387 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB).get()
, |
| 388 FROM_HERE, |
| 389 base::Bind(&LookupHistoryOnDBThread, this, input, result), |
| 390 base::Bind(&ShowHistoryOnUIThread, this, base::Owned(result))); |
| 391 } |
| 392 |
| 393 void CancelPreviousTask() { |
| 394 tracker_->TryCancel(task_id_); |
| 395 } |
| 396 |
| 397 ... |
| 398 |
| 399 private: |
| 400 CancelableTaskTracker tracker_; // Cancels all pending tasks while destru
ction. |
| 401 CancelableTaskTracker::TaskId task_id_; |
| 402 ... |
| 403 }; |
| 404 |
| 405 Since task runs on other threads, there's no guarantee it can be successfully |
| 406 canceled. |
| 407 |
| 408 When `TryCancel()` is called: |
| 409 |
| 410 * If neither task nor reply has started running, both will be canceled. |
| 411 * If task is already running or has finished running, reply will be canceled. |
| 412 * If reply is running or has finished running, cancelation is a noop. |
| 413 |
| 414 Like `base::WeakPtrFactory`, `CancelableTaskTracker` will cancel all tasks on |
| 415 destruction. |
| 416 |
| 417 ### Cancelable request __(DEPRECATED)__ |
| 418 |
| 419 Note. Cancelable request is deprecated. Please do not use it in new code. For |
| 420 canceling tasks running on the same thread, use WeakPtr. For canceling tasks |
| 421 running on a different thread, use `CancelableTaskTracker`. |
| 422 |
| 423 A cancelable request makes it easier to make requests to another thread with |
| 424 that thread returning some data to you asynchronously. Like the revokable store |
| 425 system, it uses objects that track whether the originating object is alive. When |
| 426 the calling object is deleted, the request will be canceled to prevent invalid |
| 427 callbacks. |
| 428 |
| 429 Like the revokable store system, a user of a cancelable request has |
| 430 an object (here, called a _Consumer_) that tracks whether it is alive and will |
| 431 auto-cancel any outstanding requests on deleting. |
| 432 |
| 433 class MyClass { |
| 434 void MakeRequest() { |
| 435 frontend_service->StartRequest(some_input1, some_input2, this, |
| 436 // Use base::Unretained(this) if this may cause a refcount cycle. |
| 437 base::Bind(&MyClass:RequestComplete, this)); |
| 438 } |
| 439 void RequestComplete(int status) { |
| 440 ... |
| 441 } |
| 442 |
| 443 private: |
| 444 CancelableRequestConsumer consumer_; |
| 445 }; |
| 446 |
| 447 Note that the `MyClass::RequestComplete`, is bounded with |
| 448 `base::Unretained(this)` here. |
| 449 |
| 450 The consumer also allows you to associate extra data with a request. Use |
| 451 `CancelableRequestConsumer` which will allow you to associate arbitrary data |
| 452 with the handle returned by the provider service when you invoke the |
| 453 request. The data will be automatically destroyed when the request is canceled. |
| 454 |
| 455 A service handling requests inherits from `CancelableRequestProvider`. This |
| 456 object provides methods for canceling in-flight requests, and will work with the |
| 457 consumers to make sure everything is cleaned up properly on cancel. This |
| 458 frontend service just tracks the request and sends it to a backend service on |
| 459 another thread for actual processing. It would look like this: |
| 460 |
| 461 class FrontendService : public CancelableRequestProvider { |
| 462 typedef base::Callback<void(int)> RequestCallbackType; |
| 463 |
| 464 Handle StartRequest(int some_input1, int some_input2, |
| 465 CallbackConsumer* consumer, |
| 466 const RequestCallbackType& callback) { |
| 467 scoped_refptr<CancelableRequest<FrontendService::RequestCallbackType>> |
| 468 request(new CancelableRequest(callback)); |
| 469 AddRequest(request, consumer); |
| 470 |
| 471 // Send the parameters and the request to the backend thread. |
| 472 backend_thread_->PostTask(FROM_HERE, |
| 473 base::Bind(&BackendService::DoRequest, backend_, request, |
| 474 some_input1, some_input2), 0); |
| 475 // The handle will have been set by AddRequest. |
| 476 return request->handle(); |
| 477 } |
| 478 }; |
| 479 |
| 480 The backend service runs on another thread. It does processing and forwards the |
| 481 result back to the original caller. It would look like this: |
| 482 |
| 483 class BackendService : public base::RefCountedThreadSafe<BackendService> { |
| 484 void DoRequest( |
| 485 scoped_refptr<CancelableRequest<FrontendService::RequestCallbackType>> |
| 486 request, |
| 487 int some_input1, int some_input2) { |
| 488 if (request->canceled()) |
| 489 return; |
| 490 |
| 491 ... do your processing ... |
| 492 |
| 493 // Execute ForwardResult() like you would do Run() on the base::Callback
<>. |
| 494 request->ForwardResult(return_value); |
| 495 } |
| 496 }; |
OLD | NEW |