Workers and parallelism - rebus-org/Rebus GitHub Wiki

Since everything in Rebus is async, one single worker can perform a ridiculous amount of work in parallel if that work can be awaited.

Therefore – to avoid doing too much work – the "max parallelism" concept is present, which puts a global max cap on how many messages to process in parallel.

You can configure those options by going

services.AddRebus(
    configure => configure
        .(...)
        .Options(o => {
            o.SetNumberOfWorkers(1);
            o.SetMaxParallelism(10);
        })
);

Which values to use for the settings depends on the type of work you want to perform. The following are some reasonable settings that maybe can give you some food for thought:

  • Work is predominantly asynchronous – use a few worker threads and fairly high parallelism, e.g. o.SetNumberOfWorkers(2); o.SetMaxParallelism(20);
  • Work is fast and synchronous – use a few worker threads and a matching parallelism, e.g. o.SetNumberOfWorkers(5); o.SetMaxParallelism(5); (although setting the parallelism higher will not affect anything in this case)
  • Work is slow and synchronous – use more worker threads and a matching parallelism, e.g. o.SetNumberOfWorkers(15); o.SetMaxParallelism(15); (again: setting the parallelism higher will not affect anything in this case)
  • Work must be performed in a synchronous manner, effectively serializing access to some particular resource (or to make an endpoint easier to debug during development) – use one single worker thread and a matching parallelism: o.SetNumberOfWorkers(1); o.SetMaxParallelism(1);

Since the parallelism setting puts an absolute upper cap on how many messages can be handled in parallel, it does not make sense to set the parallelism lower than the number of threads. It does not create any problems on the other hand though, it is just a waste of resources.

Defaults

Rebus will default to 1 worker thread and a max parallelism of 5.

Worker threads

Rebus will actually spawn the number of worker threads as dedicated threads. The threads will be used for polling the transport for messages.

Depending on whether the transport is asynchronous or not, the continuation after having received a message will either execute on the worker thread or on the thread pool.

Using dedicated worker threads is Rebus' default mode of operation.

Changing number of workers

At runtime, you can change the number of workers by obtaining Rebus' advanced workers API via bus.Advanced.Workers and calling SetNumberOfWorkers on it, e.g. like

bus.Advanced.Workers.SetNumberOfWorkers(1);

to set it to 1, or even

bus.Advanced.Workers.SetNumberOfWorkers(0);

to stop message processing.

The number of workers can never exceed the allowed parallelism, because that would not make any sense. It can also not go below 0 for obvious reasons, but as you can see, 0 is perfectly valid, as it just means that your Rebus instance will not do anything.

Calls to SetNumberOfWorkers are blocking and will not return until after having made the necessary adjustment to Rebus' inner worker thread pool.

TPL-based workers

Rebus also provides the options of using an alternative TPL-based worker factory, which will NOT spawn dedicated worker threads - instead, it will simply make a number of async calls to the transport.

For this to work properly, the transport must support an API for proper, asynchronous receive (which most modern transports do - but, incidentally, which the MSMQ transport does NOT!).

It can be enabled by going

services.AddRebus(
    configure => configure
        .(...)
        .Options(o => {
            o.UseTplToReceiveMessages();
        })
);

When using the TPL-based worker factory, the parallelism setting becomes redundant - the "number of workers" will define how many parallel receive operations will be commenced, and thus how many messages can potentially end up being handled concurrently.