Using the Worker Task framework

The isolate library only gives us the low-level, basic building blocks, so working with isolates in a realistic application environment can be challenging. Specifically for this purpose, the worker concurrent task executor framework was developed by Diego Rocha, available from pub package manager. It was made to abstract all the isolate managing and message passing and make concurrency in Dart as easy as possible. Worker also contains built-in error handling, so you needn't worry about that either. This recipe will show you how to use this framework so that you can concentrate on higher-level application details.

How to do it...

In the project using_worker, you can find a number of programs (using_worker1 through using_worker4) illustrating the use of this framework. The script using_worker.dart illustrates the main steps, namely creating a task, creating a worker, give a task to the worker, and process the results:

import'package:worker/worker.dart';

Worker worker;

void main() {
  // 1- Make a Task object:
  Task task = new HeavyTask();
  // 2- Construct a Worker object:
  worker = new Worker();
  // specifying poolSize and spawning lazy isolates or not:
  // worker = new Worker(poolSize: noIsol, spawnLazily: false);
  // 3- Give a task to the worker
  // 4- when the results return, process them
  worker.handle(task).then(processResult);
}
//5 - Task custom class must implement Task interface
classHeavyTask implements Task {

execute() {
  returnlongRunningComputation();
}

boollongRunningComputation() {
  varstopWatch = new Stopwatch();
  stopWatch.start();
  while (stopWatch.elapsedMilliseconds< 1000);
  stopWatch.stop();
  return true;
  }
}

processResult(result) {
  print(result);
  // process result
  // 4- Close the worker object(s)
  worker.close();
}

How it works...

First, add the package worker to pubspec.yaml, and import it in the code. A task is something that needs to be executed. This is an abstract class in the library, providing an interface for tasks and specifying that your custom Task class must implement the execute method, which returns a result. In our script the custom Task is HeavyTask, and execute simulates a long running computation using the Stopwatch class.

A worker object creates and manages a pool containing a number (poolSize) of isolates providing you with an easy way to perform blocking tasks concurrently. It spawns isolates lazily as Tasks are required to execute; the spawned isolates are available in a queue named isolates. The currently active isolates are stored in an iterable called workingIsolates, and the free isolates can be retrieved from an iterable called availableIsolates.

In the language of isolates, this is what happens; when a Worker instance is created, it starts listening to the ReceivePort of the current isolate, and a pool of isolates is created. The isolates in this pool are used to process any task passed to the worker. When a task is passed to the worker to be handled, it returns a Future. This Future will only complete when the Task is executed or when it fails to be executed.

By default, worker is created with poolSize equal to the number of processors on the machine (Platform.numberOfProcessors), and the isolates are spawned lazily (that is, only when needed). You can, however, change the number of isolates and also whether the isolates are spawned lazily or not using optional constructor parameters, as follows:

worker = new Worker(poolSize: noIsol, spawnLazily: false);

The work is started by handing over the task to the worker with worker.handle(task). The handle method takes a task, and returns a Future object, so we process the result when it is returned with worker.handle(task).then(processResult);.

Also, make sure that, after the processing is done, worker gets closed or the program keeps running.

When executing a number of tasks, you can add them to a List<Future> task as shown in the following code:

intnoTasks = 500;
for (var i=1; i<=noTasks; i++) {
  tasks.add(worker.handle(new HeavyTask()));
}

And then process them with:
Future.wait(tasks).then(processResult);

This mechanism is illustrated in the using_worker2 and using_worker3 examples.

There's more…

Another good scenario to use isolates or use worker class in particular is that of a web server that has a few different services. Perhaps one of those services has to do some calculations and takes a while to respond, whereas, the others are light and respond right away. When the heavy service is requested not using isolates, all other requests are blocked, waiting to be processed, even if they are requesting one of the light services. If you do use an isolate or worker and run the heavy service in parallel, it will take roughly the same time to respond to the first request, but all the subsequent requests won't have to wait.

See also

  • See the Using isolates in the Dart VM recipe in this chapter for background information on isolates
  • See the Error handling with Futures recipe in this chapter for more information on how to use Futures
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset