dadhi / DryIoc

DryIoc is fast, small, full-featured IoC Container for .NET
MIT License
988 stars 122 forks source link

Scope ist lost on CTRL+C #583

Closed WolfBublitz closed 11 months ago

WolfBublitz commented 1 year ago

Hallo,

I am currently facing a weird situation with a container using the AsyncExecutionFlowScopeContext. I have tracked down my issue to the following small example:

using System;
using System.Threading;
using System.Threading.Tasks;
using DryIoc;

IContainer container = new Container(scopeContext: new AsyncExecutionFlowScopeContext());

CancellationTokenSource cts = new();

using (IResolverContext scope = container.OpenScope("MyScope", trackInParent: true))
{
    Console.CancelKeyPress += (sender, args) =>
    {
        Console.WriteLine("Canceling");
        Console.WriteLine(scope.CurrentScope?.ToString() ?? "No scope");

        cts.Cancel();
        args.Cancel = true;
    };

    while (!cts.IsCancellationRequested)
    {
        Console.WriteLine(scope.CurrentScope.ToString());

        await Task.Delay(1000).ConfigureAwait(false);
    }
}

Console.WriteLine("Scope disposed");

While the application is running it prints out

{Name=MyScope} {Name=MyScope} ...

as expected. But when I now hit CTRL+C the following output comes

Canceling No scope Scope disposed

I would have expected the scope to stay as long the using block was not left.

Is this a bug or am I doing something wrong here?

WolfBublitz commented 1 year ago

I have created a custom (very simple) implementation of IScopeContext that solves my problem:

internal sealed class ScopeContext : IScopeContext
{
    private static IScope scope;

    public IScope GetCurrentOrDefault() => scope;

    public IScope SetCurrent(SetCurrentScopeHandler changeCurrentScope) => scope = changeCurrentScope(GetCurrentOrDefault());

    public void Dispose()
    {
    }
}
dadhi commented 1 year ago

@WolfBublitz Interesting.. It probably relates to the behavior of AsyncLocal in your case. I am glad that you went on implementing you context. At least having the interface payed it bill here.

WolfBublitz commented 11 months ago

Yes the interface saved my day ;-).

dadhi commented 11 months ago

@WolfBublitz, Hi here

AsyncExecutionFlowScopeContext is supposed to flow the current scope across the await call boundaries, e.g. in the case of web request int ASP.NET. So the scope is preserved ain a new a possibly new pooled thread after calling the await Task.Delay(1000).ConfigureAwait(false);

The cancel event however happens in the initial thread and this thread doesn't have the scope anymore, it was moved to the new thread with async/await flow. That's why you see the "No scope" output.

So if you want a static scope context to make scope shared between all threads, then you did right by introducing your own IScopeContext with the static IScope scope.