blob: 87ee02c3174a785b19c77b06691de23afac127b1 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
= Asynchronous APIs
== Overview
Many Ignite APIs have asynchronous variants, for example, `void ICache.Put` and `Task ICache.PutAsync`.
Async APIs allow us to write link:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/[efficient non-blocking code]:
[source,csharp]
----
ICache<int, string> cache = ignite.GetOrCreateCache<int, string>("name");
// Sync, blocks thread on every call.
cache.Put(1, "Hello");
string hello = cache.Get(1);
// Async, does not block threads.
await cache.PutAsync(1, "Hello");
string hello = await cache.GetAsync(1);
----
With async APIs, current thread is not blocked while we wait for the cache operation to complete;
the thread is returned to the thread pool and can perform other work.
When the async operation completes, our method resumes execution - either on the same thread, or on a different one -
depending on the environment and the configuration. This is called "async continuation".
== Async Continuations
Unless specified otherwise, Ignite executes async continuations on the link:https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool[.NET Thread Pool], which is safe and does not require any special care.
=== Thin Client
All thin client async APIs use link:https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool[.NET Thread Pool.] for async continuations.
=== Thick Cache
Callbacks for asynchronous cache operations on server and thick client nodes are invoked by using Java `ForkJoinPool#commonPool`, unless a different executor is configured with `IgniteConfiguration.AsyncContinuationExecutor`.
* This default executor is safe for any operations inside the callback.
* Default behavior was changed in Ignite 2.11. Before that, async cache operation callbacks were called from an Ignite system pool (so-called "striped pool").
* To restore the previous behavior, use `IgniteConfiguration.AsyncContinuationExecutor = AsyncContinuationExecutor.UnsafeSynchronous`.
** Previous behavior can provide a small performance improvement, because callbacks are executed without any indirection or scheduling.
** UNSAFE: cache operations cannot proceed while system threads execute callbacks, and deadlocks are possible if other cache operations are invoked from the callback.
[IMPORTANT]
====
[discrete]
=== *Ignite 2.10 and before*: possibility of deadlocks and system pool starvation
In Ignite versions 2.10 and before, system pool is used to run async continuations,
which means that `GetAsync` call in the code above is executed by the system thread.
This can lead to deadlocks if user code blocks the thread, or cause starvation because system thread is busy
running user code instead of performing cache operations.
To enable safe behavior, move continuations to .NET Thread Pool manually:
[source,csharp]
----
await cache.PutAsync(1, "Hello").ContinueWith(
t => {},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
string hello = await cache.GetAsync(1).ContinueWith(
t => t.Result,
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
----
Tip: use an extension method to reduce verbosity.
====
=== Compute
*Ignite 2.11 and later*: all `ICompute` async APIs use .NET Thread Pool to run async continuations.
*Ignite 2.10 and before*: Compute async continuations are executed on link:perf-and-troubleshooting/thread-pools-tuning[Ignite public pool].
To reduce the load on the public pool, it is recommended to use the same `ContinueWith` approach as above:
[source,csharp]
----
await compute.CallAsync(new MyAction()).ContinueWith(
t => t.Result,
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
----
This will move the continuation from Ignite public pool (reserved for Compute functionality) to the .NET thread pool (`TaskScheduler.Default`).
== ConfigureAwait
`Task.ConfigureAwait` method can be used as usual with all Ignite async APIs.
See link:https://devblogs.microsoft.com/dotnet/configureawait-faq/[ConfigureAwait FAQ] for more details.