I have a block of code that throws:
is registered using the 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope`
return
for the Task
(at higher volumes, but ok at lower processing volumes)await
the Task
, Simple Injector does not throw.I appreciate this may be more of a async focused question as opposed to Simple Injector, but any insights why replacing return
with await
resolves this?
Thanks in advance, I am concerned whilst "working" using await
is it potentially hiding a larger issue.
Background:
I have the following loop that deques items (tasks to be dispatched) by a worker:
void Loop()
{
while (cancellationToken.IsCancellationRequested == false)
{
item = await this.queue.Reader.ReadAsync(cancellationToken);
await DispatchItemAsync(item, (item, dispatcher) =>
{
return dispatcher
.SendAsync(((ISendItem)item).GetHandler, item.Message, item.CancellationToken)
.ContinueWith(t => item.TaskCompletionSourceWrapper.SetResult(t.Result), TaskContinuationOptions.RunContinuationsAsynchronously);
});
}
}
The DispatchItemAsync
from the above loop is below:
protected override async Task DispatchItemAsync(
IQueueItem item, Func<IQueueItem, IThreadStrategy, Task> dispatchFunc)
{
// cast the passed item from channel queue
var queueItemWithStack = item as IQueueItemWithStack;
using (AsyncScopedLifestyle.BeginScope(this.container))
{
var dispatcher = container.GetInstance<InParallel>();
// the above is an interface of delegates that is used to call functions
// return throws SimpleInjector outside of scope exception (intermittent,
// always for high request volume)
return dispatchFunc(queueItemWithStack, dispatcher);
// using await no exception is thrown
// await dispatchFunc(queueItemWithStack, dispatcher);
}
}
With InParallel
containing the function which is called by the dispatchFunc
line, the below is (eventually via a chain) called:
public Task<object> SendAsync(
Func<SendFunction> getHandler,
object request,
CancellationToken cancellationToken = default)
{
return this
.inCaller
.SendAsync(getHandler, request, cancellationToken)
.ContinueWith(t =>
{
// snip some code
// the below throws if DispatchItemAsync call us with return
// but is OK if DispatchItemAsync called us with await instead
return t.Result;
});
}
The exception occurs on the above ContinueWith
when the t.Result
is accessed:
CommandHandler is registered using the 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope. Please see https://simpleinjector.org/scoped for more information about how apply lifestyles and manage scopes.
By not awaiting a Task
, you are executing the Task
in parallel with the primary operation. Parallel means the code becomes multi threaded.
This means that the original Scope
will likely get disposed before the Task
finished executing. This causes the exception you are experiencing. But in other cases, the Scope
might get disposed of during the execution of the Task
. This will cause objects getting disposed of while the Task
is running. This can cause weird multi-threading issues.
What this means is that, in your case, you should absolutely await the Task
that is returned from dispatchFunc
.