how to cast async Func or Action to Delegate and invoke it
Asked Answered
B

1

6

I'm trying to make this code working:

protected async Task RunIsolated<TServ1, TServ2>(Action<TServ1, TServ2> action)
{
    await RunInScope(action, typeof(TServ1), typeof(TServ2));
}

protected async Task<TResult> RunIsolatedForResult<TService, TResult>(Func<TService, TResult> func)
{
    return (TResult) await RunInScope(func, typeof(TService));
}

private Task<object> RunInScope(Delegate d, params object[] args)
{
     using (var scope = _serviceProvider.CreateScope())
     {
         object[] parameters = args.Cast<Type>().Select(t => scope.ServiceProvider.GetService(t)).ToArray();
         return Task.FromResult(d.DynamicInvoke(parameters));
     }
}

this work for sync version of code, like this:

await RunIsolated<Service>(serv => serv.SaveAsync(item).Wait());

but don't work (db operation throw an exception) for async version of the same code

await RunIsolated<Service>(async serv => await serv.SaveAsync(item));

Is it somehow possible to convert async Action or Func to Delegate and invoke it without loosing async state?

Bradshaw answered 28/12, 2017 at 11:50 Comment(2)
So that fails at runtime? With which exception?Ramsgate
@Ramsgate exception is regarding SQL Operation already invoked or in progress, probably due to the fact that some operation start before previous finished. However it's irrelevant to this question. Idea is not solving this exact db exception, but understand how not loosing async state, because it's a root of this exact problem.Bradshaw
R
6

You need to create new overload which accepts Func<Task>. Right now, anonymous async function you pass here

await RunIsolated<Service>(async serv => await serv.SaveAsync(item));

Is treated as Action, which means that is async void method basically, with all corresponding drawbacks. Instead you have to do something like this (simplified to use basic Action and Func, adjust as needed to your situation):

protected Task RunIsolated(Action action) {
    return RunInScope(action);
}

protected Task RunIsolated(Func<Task> action) {
    return RunInScope(action);
}

protected Task<TResult> RunIsolatedForResult<TResult>(Func<Task<TResult>> action) {
    return RunInScopeWithResult<TResult>(action);
}

protected Task<TResult> RunIsolatedForResult<TResult>(Func<TResult> action) {
    return RunInScopeWithResult<TResult>(action);
}

private async Task RunInScope(Delegate d, params object[] args) {
    // do some stuff
    using (var scope = _serviceProvider.CreateScope()) {
        object[] parameters = args.Cast<Type>().Select(t => scope.ServiceProvider.GetService(t)).ToArray();
        var result = d.DynamicInvoke(parameters);
        var resultTask = result as Task;
        if (resultTask != null) {
            await resultTask;
        }
    }
}

private async Task<TResult> RunInScopeWithResult<TResult>(Delegate d, params object[] args) {
    // do some stuff
    using (var scope = _serviceProvider.CreateScope()) {
        object[] parameters = args.Cast<Type>().Select(t => scope.ServiceProvider.GetService(t)).ToArray();
        var result = d.DynamicInvoke(parameters);
        var resultTask = result as Task<TResult>;
        if (resultTask != null) {
            return await resultTask;
        }
        return (TResult) result;
    }
}
Ramsgate answered 28/12, 2017 at 12:11 Comment(5)
still not working (the same error as before). delegate invocation return result with values like that: Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"Bradshaw
That's correct, it returned Task. Then you casted that to (Task), returned back to the caller, and caller awaited the result? Maybe you can publish updated version of your code.Ramsgate
I feel like it will not work. Since delegate invocation should be inside using block, and actually the whole excution should be inside using block (see my initial question), if we will simply return task out from using scope, at the moment of execution scope could be already disposed and this will cause errors.Bradshaw
@Bradshaw yes I missed the whole point is that scope. Updated answer.Ramsgate
Thanks, that's indeed work. I modify code a little to work for me, however in any case your initial tip that it should be Func<Task> is essential.Bradshaw

© 2022 - 2024 — McMap. All rights reserved.