Dynamic in Task.Run

I have a long job with the same name in unrelated classes. I tried to use this code in a generic method using dynamic. I am getting the following error

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException was not handled by user code Message = Cannot implicitly convert 'void' to 'object'

I tried to highlight the code as follows

class Program
{
    static void Main(string[] args)
    {
        MainAsync();
        Console.ReadKey();
    }
    static async void MainAsync()
    {
        var classA = new ClassA();
        var classB = new ClassB();
        await RunTask1(classA);
        await RunTask1(classB);
        await RunTask(classA);
        await RunTask(classB);
    }
    static async Task RunTask(dynamic val)
    {
        await Task.Run(() => val.CommonLongRunningTask());
    }
    static async Task RunTask1(ClassA val)
    {
        await Task.Run(() => val.CommonLongRunningTask());
    }
    static async Task RunTask1(ClassB val)
    {
        await Task.Run(() => val.CommonLongRunningTask());
    }
}
internal class ClassA
{
    public void CommonLongRunningTask()
    {
        Console.WriteLine("Class A CommonLongRunningTask");
    }
}
internal class ClassB
{
    public void CommonLongRunningTask()
    {
        Console.WriteLine("Class B CommonLongRunningTask");
    }
}

      

If I pass the object itself (RunTask1) instead of dynamic it works. I am trying to figure out what happens when I pass dynamically.

+3


source to share


4 answers


I haven't been able to track it down in some language yet, but it looks like you can't have a lambda expression with dynamic expression. Update: an expression involving a is dynamic

always of type dynamic

, regardless of whether a void method call exists, see Updating Language Aspects

Lambda operation:

private static async Task RunTask(dynamic val)
{
    await Task.Run(() =>
    {
        val.CommonLongRunningTask();
    });
}

      

Update:

Effectively what happens here when the compiler encounters this:

() => val.CommonLongRunningTask()

      

it interprets on it as equivalent:



() => {return val.CommonLongRunningTask();}

      

... since it is not known at compile time that whatever you call val

returns nothing. At run time, it encounters an expression val.CommonLongRunningTask()

that has a type void

, but cannot return

that value even for the lowest common denominator, object

and thus throws an exception with the messageCannot implicitly convert type 'void' to 'object'

You will have the same exception if you republish yours RunTask

like this:

    private static async Task RunTask(dynamic val)
    {
        await Task.Run(() =>
        {
            return val.CommonLongRunningTask();
        });
    }

      

Language aspects

After a bit more research / discussion in section 7.5.2 describing the C # spec, why this happens. Basically anything related to dynamics is itself a dynamic type (since at compile time the compiler cannot know how things will be related) and therefore it treats "val.CommonLongRunningTask ()" as something that returns a dynamic ( and therefore has a runtime error when it implements it unchanged at runtime).

+6


source


Create an interface for your generic method:

public interface ICommonTask
{
    void CommonLongRunningTask();
}

      



Implement this in both classes and use instead:

static async Task RunTask(ICommonTask val)

class ClassA : ICommonTask

class ClassB : ICommonTask

      

+1


source


The problem is that:

() => val.CommonLongRunningTask()

      

means something:

delegate
{
    return val.CommonLongRunningTask();
}

      

What happens at runtime because the method CommonLongRunningTask

you are binding with does not have a return type.

+1


source


I just had the same problem, and in response to Peter's answer on why this is happening, I came up with a solution to get Task.Run from a dynamic void method to work independently.

rewritten:

() => val.CommonLongRunningTask()

      

and

() => ForceDynamicExpressionBackToVoid(() => val.CommonLongRunningTask())

      

with the following method:

private void ForceDynamicExpressionBackToVoid(Action @dynamic)
{
    @dynamic.Invoke();
}

      

The caveat is that you should be aware that any instance of CommonLongRunningTask that you end up with at runtime is guaranteed to return void, otherwise you will get another exception.

0


source







All Articles