Service Cancellation
Service lifetime token can be assigned with 'cancelToken:' parameter.
// Create cancel signal source
CancellationTokenSource serviceCancelSource = new CancellationTokenSource();
// Get token
CancellationToken serviceCancelToken = serviceCancelSource.Token;
// Create service with cancel token
IService service = Services.Create((ServiceHandlers.Instance, new DelayHandler()), cancelToken: serviceCancelToken);
With ServiceBuilder the .AddCancellationToken(cancellationToken) method adds a token.
// Create cancel signal source
CancellationTokenSource serviceCancelSource = new CancellationTokenSource();
// Get token
CancellationToken serviceCancelToken = serviceCancelSource.Token;
// Create service with cancel token
IService service =
new ServiceBuilder()
.WithoutCache()
.AddHandlers(ServiceHandlers.Instance)
.AddHandler(new DelayHandler())
.AddCancellationToken(serviceCancelToken)
.Build();
If this token is canceled, then all resolving stops and on-going queries throw OperationCanceledException.
try
{
// Create request
IRequestFor<Type> request = new TypeRequest("System.String");
// Issue request to canceled service
Task<Type?> task = service.GetAsync<IRequestFor<Type>, Type>(request);
// Cancel service
serviceCancelSource.Cancel();
// Issue request to canceled service
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
Query Cancellation
Query specific cancel token is added as 'cancelToken:' parameter.
// Create cancel source
CancellationTokenSource queryCancelSource = new CancellationTokenSource();
// Get token
CancellationToken queryCancelToken = queryCancelSource.Token;
// Issue request
Task<Type?> task = service.GetAsync<IRequestFor<Type>, Type>(request, cancelToken: queryCancelToken);
The service resolver checks cancel token between handlers, if query is aborted, then OperationCanceledException is thrown.
try
{
// Issue request
Task<Type?> task = service.GetAsync<IRequestFor<Type>, Type>(request, cancelToken: queryCancelToken);
// Cancel
queryCancelSource.Cancel();
// Wait
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
Query cancellation with cache
If query is cached, then the query specific cancel token won't be applied.
try
{
// Create service WITH cache
IService service = Services.Create((ServiceHandlers.Instance, new DelayHandler()), CachePolicies.DefaultYes);
// Create request
IRequestFor<Type> request = new TypeRequest("System.String");
// Create cancel source
CancellationTokenSource queryCancelSource = new CancellationTokenSource();
// Get token
CancellationToken queryCancelToken = queryCancelSource.Token;
// Issue request
Task<Type?> task = service.GetAsync<IRequestFor<Type>, Type>(request, cancelToken: queryCancelToken);
// Cancel (WON'T BE APPLIED due to caching)
queryCancelSource.Cancel();
// Wait
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
However if called with .GetAsyncQuickAbort() then execution returns to the caller on cancel signal, though the query will continue in the background and result will remain cached.
try
{
// Create service WITH cache
IService service = Services.Create((ServiceHandlers.Instance, new DelayHandler()), CachePolicies.DefaultYes);
// Create request
IRequestFor<Type> request = new TypeRequest("System.String");
// Create cancel source
CancellationTokenSource queryCancelSource = new CancellationTokenSource();
// Get token
CancellationToken queryCancelToken = queryCancelSource.Token;
// Issue request
Task<Type?> task = service.GetAsyncQuickAbort<IRequestFor<Type>, Type>(request, cancelToken: queryCancelToken);
// Cancel
queryCancelSource.Cancel();
// Wait (Aborts immediately, but resolve continues in the background and result is cached)
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
Cancellable query should be designed to implement IRequestNotToBeCached.
public record MyQuery : IRequestNotToBeCached;
Handler implementation
Handler implementation gets access to cancel token from query.CancellationToken property. Typical implementation doesn't need to check for cancellation, but if implementation does blocking or makes API calls that may put thread into sleep, then it should check for cancel signal.
If both service lifetime token and call tokens are used, then the query.CancellationToken represents the union of these the two.
/// <summary>Example: Delays handling, but aborts on cancel token.</summary>
public class DelayHandler : IHandlerAsync
{
[Order(-1000)]
public async Task HandleAsync<Request, Response>(IQuery<Request, Response> query) where Request : notnull
{
// Got cancel token
if (query.CancellationToken.CanBeCanceled)
{
await Task.Delay(100, query.CancellationToken);
}
// Did not get cancel token
else
{
await Task.Delay(100);
}
}
}
Full Example
Full example
using System;
using System.Threading;
using System.Threading.Tasks;
using Avalanche.Module;
using Avalanche.Service;
public class service_cancel
{
public static async Task Run()
{
// Service cancellation
{
// <01>
// Create cancel signal source
CancellationTokenSource serviceCancelSource = new CancellationTokenSource();
// Get token
CancellationToken serviceCancelToken = serviceCancelSource.Token;
// Create service with cancel token
IService service = Services.Create((ServiceHandlers.Instance, new DelayHandler()), cancelToken: serviceCancelToken);
// </01>
}
{
// <02>
// Create cancel signal source
CancellationTokenSource serviceCancelSource = new CancellationTokenSource();
// Get token
CancellationToken serviceCancelToken = serviceCancelSource.Token;
// Create service with cancel token
IService service =
new ServiceBuilder()
.WithoutCache()
.AddHandlers(ServiceHandlers.Instance)
.AddHandler(new DelayHandler())
.AddCancellationToken(serviceCancelToken)
.Build();
// </02>
// <03>
try
{
// Create request
IRequestFor<Type> request = new TypeRequest("System.String");
// Issue request to canceled service
Task<Type?> task = service.GetAsync<IRequestFor<Type>, Type>(request);
// Cancel service
serviceCancelSource.Cancel();
// Issue request to canceled service
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
// </03>
}
// Query cancellation
{
try
{
// Create service
IService service = Services.Create((ServiceHandlers.Instance, new DelayHandler()));
// Create request
IRequestFor<Type> request = new TypeRequest("System.String");
// <11>
// Create cancel source
CancellationTokenSource queryCancelSource = new CancellationTokenSource();
// Get token
CancellationToken queryCancelToken = queryCancelSource.Token;
// Issue request
Task<Type?> task = service.GetAsync<IRequestFor<Type>, Type>(request, cancelToken: queryCancelToken);
// </11>
// Cancel
queryCancelSource.Cancel();
// Wait
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
}
{
// Create service
IService service = Services.Create((ServiceHandlers.Instance, new DelayHandler()));
// Create request
IRequestFor<Type> request = new TypeRequest("System.String");
// Create cancel source
CancellationTokenSource queryCancelSource = new CancellationTokenSource();
// Get token
CancellationToken queryCancelToken = queryCancelSource.Token;
// <12>
try
{
// Issue request
Task<Type?> task = service.GetAsync<IRequestFor<Type>, Type>(request, cancelToken: queryCancelToken);
// Cancel
queryCancelSource.Cancel();
// Wait
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
// </12>
}
//
{
// <13>
try
{
// Create service WITH cache
IService service = Services.Create((ServiceHandlers.Instance, new DelayHandler()), CachePolicies.DefaultYes);
// Create request
IRequestFor<Type> request = new TypeRequest("System.String");
// Create cancel source
CancellationTokenSource queryCancelSource = new CancellationTokenSource();
// Get token
CancellationToken queryCancelToken = queryCancelSource.Token;
// Issue request
Task<Type?> task = service.GetAsync<IRequestFor<Type>, Type>(request, cancelToken: queryCancelToken);
// Cancel (WON'T BE APPLIED due to caching)
queryCancelSource.Cancel();
// Wait
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
// </13>
}
{
// <14>
try
{
// Create service WITH cache
IService service = Services.Create((ServiceHandlers.Instance, new DelayHandler()), CachePolicies.DefaultYes);
// Create request
IRequestFor<Type> request = new TypeRequest("System.String");
// Create cancel source
CancellationTokenSource queryCancelSource = new CancellationTokenSource();
// Get token
CancellationToken queryCancelToken = queryCancelSource.Token;
// Issue request
Task<Type?> task = service.GetAsyncQuickAbort<IRequestFor<Type>, Type>(request, cancelToken: queryCancelToken);
// Cancel
queryCancelSource.Cancel();
// Wait (Aborts immediately, but resolve continues in the background and result is cached)
Type? type = await task;
}
catch (OperationCanceledException)
{
// Handle cancellation
Console.WriteLine("Canceled");
}
// </14>
}
}
}
// <20>
/// <summary>Example: Delays handling, but aborts on cancel token.</summary>
public class DelayHandler : IHandlerAsync
{
[Order(-1000)]
public async Task HandleAsync<Request, Response>(IQuery<Request, Response> query) where Request : notnull
{
// Got cancel token
if (query.CancellationToken.CanBeCanceled)
{
await Task.Delay(100, query.CancellationToken);
}
// Did not get cancel token
else
{
await Task.Delay(100);
}
}
}
// </20>
// <15>
public record MyQuery : IRequestNotToBeCached;
// </15>