Skip to content

Latest commit

 

History

History
239 lines (173 loc) · 11.9 KB

dispatchers.md

File metadata and controls

239 lines (173 loc) · 11.9 KB
uid title
dispatchers
Dispatchers

Dispatchers

What Do Dispatchers Do?

Dispatchers are responsible for scheduling all code that run inside the ActorSystem. Dispatchers are one of the most important parts of Akka.NET, as they control the throughput and time share for each of the actors, giving each one a fair share of resources.

By default, all actors share a single Global Dispatcher. Unless you change the configuration, this dispatcher uses the .NET Thread Pool behind the scenes, which is optimized for most common scenarios. That means the default configuration should be good enough for most cases.

Why should I use different dispatchers?

When messages arrive in the actor's mailbox, the dispatcher schedules the delivery of messages in batches, and tries to deliver the entire batch before releasing the thread to another actor. While the default configuration is good enough for most scenarios, you may want to change (through configuration) how much time the scheduler should spend running each actor.

There are some other common reasons to select a different dispatcher. These reasons include (but are not limited to):

  • isolating one or more actors to specific threads in order to:
    • ensure high-load actors don't starve the system by consuming too much cpu-time;
    • ensure important actors always have a dedicated thread to do their job;
    • create bulkheads, ensuring problems created in one part of the system do not leak to others;
  • allow actors to execute in a specific SyncrhonizationContext;

Note

Consider using custom dispatchers for special cases only. Correctly configuring dispatchers requires some understanding of how the framework works. Custom dispatchers should not be considered the default solution for performance problems. It's considered normal for complex applications to have one or a few custom dispatchers, it's not usual for most or all actors in a system to require a custom dispatcher configuration.

Dispatchers vs. Dispatcher Configurations

Throughout this documentation and most Akka literature available, the term dispatcher is used to refer to dispatcher configurations, but they are in fact different things.

  • Dispatchers are low level components that are responsible for scheduling code execution in the system. These components are built into Akka.NET, there is a fixed number of them and you don't need to create or change them.

  • Dispatcher Configurations are custom settings you can create to make use of dispatchers in specific ways. There are some built-in dispatcher configurations, and you can create as many as you need for your applications.

Therefore, when you read about "creating a custom dispatcher" it usually means "using a custom configuration for one of the built-in dispatchers".

Configuring Dispatchers

You can define a custom dispatcher configuration using a HOCON configuration section.

The example below creates a custom dispatcher called my-dispatcher that can be set in one or more actors during deployment:

my-dispatcher {
    type = Dispatcher
    throughput = 100
    throughput-deadline-time = 0ms
}

You can then set actor's dispatcher using the deployment configuration:

akka.actor.deployment {
    /my-actor {
        dispatcher = my-dispatcher
    }
}

Or you can also set it up in code:

system.ActorOf(Props.Create<MyActor>().WithDispatcher("my-dispatcher"), "my-actor");

Built-in Dispatcher Configurations

Some dispatcher configurations are available out-of-the-box for convenience. You can use them during actor deployment, as described above.

  • default-dispatcher - A configuration that uses the ThreadPoolDispatcher. As the name says, this is the default dispatcher configuration used by the global dispatcher, and you don't need to define anything during deployment to use it.
  • internal-dispatcher - To protect the internal Actors that is spawned by the various Akka modules, a separate internal dispatcher is used by default.
  • task-dispatcher - A configuration that uses the TaskDispatcher.
  • default-fork-join-dispatcher - A configuration that uses the ForkJoinDispatcher.
  • synchronized-dispatcher - A configuration that uses the SynchronizedDispatcher.
  • channel-executor - new as of v1.4.19, the ChannelExecutor is used to run on top of the .NET ThreadPool and allow Akka.NET to dynamically scale thread usage up and down with demand in exchange for better CPU and throughput performance.

Built-in Dispatchers

These are the underlying dispatchers built-in to Akka.NET:

ThreadPoolDispatcher

It schedules code to run in the .NET Thread Pool, which is good enough for most cases.

The type used in the HOCON configuration for this dispatcher is just Dispatcher.

custom-dispatcher {
type = Dispatcher
throughput = 100
}

Note

While each configuration can have it's own throughput settings, all dispatchers using this type will run in the same default .NET Thread Pool.

TaskDispatcher

The TaskDispatcher uses the TPL infrastructure to schedule code execution. This dispatcher is very similar to the Thread PoolDispatcher, but may be used in some rare scenarios where the thread pool isn't available.

custom-task-dispatcher {
  type = TaskDispatcher
  throughput = 100
}

PinnedDispatcher

The PinnedDispatcher uses a single dedicated thread to schedule code executions. Ideally, this dispatcher should be using sparingly.

custom-dedicated-dispatcher {
  type = PinnedDispatcher
}

ForkJoinDispatcher

The ForkJoinDispatcher uses a dedicated threadpool to schedule code execution. You can use this scheduler isolate some actors from the rest of the system. Each dispatcher configuration will have it's own thread pool.

This is the configuration for the default-fork-join-dispatcher. You may use this as example for custom fork-join dispatchers.

default-fork-join-dispatcher {
  type = ForkJoinDispatcher
  throughput = 100
  dedicated-thread-pool {
	  thread-count = 3
	  deadlock-timeout = 3s
	  threadtype = background
  }
}
  • thread-count - The number of threads dedicated to this dispatcher.
  • deadlock-timeout - The amount of time to wait before considering the thread as deadlocked. By default no timeout is set, meaning code can run in the threads for as long as they need. If you set a value, once the timeout is reached the thread will be aborted and a new threads will take it's place. Set this value carefully, as very low values may cause loss of work.
  • threadtype - Must be background or foreground. This setting helps define how .NET handles the thread.

SynchronizedDispatcher

The SynchronizedDispatcher uses the current SynchronizationContext to schedule executions.

You may use this dispatcher to create actors that update UIs in a reactive manner. An application that displays real-time updates of stock prices may have a dedicated actor to update the UI controls directly for example.

Note

As a general rule, actors running in this dispatcher shouldn't do much work. Avoid doing any extra work that may be done by actors running in other pools.

This is the configuration for the synchronized-dispatcher. You may use this as example for custom fork-join dispatchers.

synchronized-dispatcher {
  type = "SynchronizedDispatcher"
  throughput = 10
}

In order to use this dispatcher, you must create the actor from the synchronization context you want to run-it. For example:

private void Form1_Load(object sender, System.EventArgs e)
{
  system.ActorOf(Props.Create<UIWorker>().WithDispatcher("synchronized-dispatcher"), "ui-worker");
}

ChannelExecutor

In Akka.NET v1.4.19 we will be introducing an opt-in feature, the ChannelExecutor - a new dispatcher type that re-uses the same configuration as a ForkJoinDispatcher but runs entirely on top of the .NET ThreadPool and is able to take advantage of dynamic thread pool scaling to size / resize workloads on the fly.

During its initial development and benchmarks, we observed the following:

  1. The ChannelExecutor tremendously reduced idle CPU and max busy CPU even during peak message throughput, primarily as a result of dynamically shrinking the total ThreadPool to only the necessary size. This resolves one of the largest complaints large users of Akka.NET have today.
  2. The ChannelExecutor actually beat the ForkJoinDispatcher and others on performance even in environments like Docker and bare metal on Windows.

Note

We are in the process of gathering data from users on how well ChannelExecutor performs in the real world. If you are interested in trying out the ChannelExecutor, please read the directions in this document and then comment on the "Akka.NET v1.4.19: ChannelExecutor performance data" discussion thread.

The ChannelExectuor re-uses the same threading settings as the ForkJoinExecutor to determine its effective upper and lower parallelism limits, and you can configure the ChannelExecutor to run inside your ActorSystem via the following HOCON configuration:

akka.actor.default-dispatcher = {
    executor = channel-executor
    fork-join-executor { #channelexecutor will re-use these settings
      parallelism-min = 2
      parallelism-factor = 1
      parallelism-max = 64
    }
}

akka.actor.internal-dispatcher = {
    executor = channel-executor
    throughput = 5
    fork-join-executor {
      parallelism-min = 4
      parallelism-factor = 1.0
      parallelism-max = 64
    }
}

akka.remote.default-remote-dispatcher {
    type = Dispatcher
    executor = channel-executor
    fork-join-executor {
      parallelism-min = 2
      parallelism-factor = 0.5
      parallelism-max = 16
    }
}

akka.remote.backoff-remote-dispatcher {
  executor = channel-executor
  fork-join-executor {
    parallelism-min = 2
    parallelism-max = 2
  }
}

This will enable the ChannelExecutor to run everywhere and all Akka.NET loads, with the exception of anything you manually allocate onto a ForkJoinDispatcher or PinnedDispatcher, will be managed by the ThreadPool.

Common Dispatcher Configuration

The following configuration keys are available for any dispatcher configuration:

  • type - (Required) The type of dispatcher to be used: Dispatcher, TaskDispatcher, PinnedDispatcher, ForkJoinDispatcher or SynchronizedDispatcher.
  • throughput - (Required) The maximum # of messages processed each time the actor is activated. Most dispatchers default to 100.
  • throughput-deadline-time - The maximum amount of time to process messages when the actor is activated, or 0 for no limit. The default is 0.

Note

The throughput-deadline-time is used as a best effort, not as a hard limit. This means that if a message takes more time than the deadline allows, Akka.NET won't interrupt the process. Instead it will wait for it to finish before giving turn to the next actor.

Dispatcher aliases

When a dispatcher is looked up, and the given setting contains a string rather than a dispatcher config block, the lookup will treat it as an alias, and follow that string to an alternate location for a dispatcher config. If the dispatcher config is referenced both through an alias and through the absolute path only one dispatcher will be used and shared among the two ids.