简体   繁体   中英

dynamic in Task.Run

I have a long running task with the same name in unrelated classes. I was trying to have this code in common method using dynamic. I am getting following error

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

I tried to isolate the code to the following

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 understand what is happening when I am passing in dynamic.

I haven't yet been able to track it back to something in the language, but it appears you can't have an expression lambda with dynamic expression. Update: an expression involving a dynamic is always of type dynamic regardless of whether there's a void method call, see Language Aspects update

A statement lambda works:

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

Update:

Effectively what's going on here is when the compiler encounters this:

() => val.CommonLongRunningTask()

it interprets at it as the equivalent of:

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

...since it doesn't know at compile-time that anything you call on val doesn't return anything. At run-time, it encounters the expression val.CommonLongRunningTask() which is of type void but cannot return that value to even the lowest common denominator object and thus throws the exception with message Cannot implicitly convert type 'void' to 'object'

You'd have the very same exception if you re-wrote your RunTask as follows:

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

Language Aspects

After a bit more research/discussion it appears section 7.5.2 of the C# spec details why this is happening. Essentially anything involving a dynamic is itself a dynamic type (because at compile time the compiler cannot know how things will be bound) and thus it views "val.CommonLongRunningTask()" as something that returns dynamic (and thus has a run-time error when it realizes it's void at run-time).

Create a interface for your common method:

public interface ICommonTask
{
    void CommonLongRunningTask();
}

Implement this in both classes and use it instead:

static async Task RunTask(ICommonTask val)

class ClassA : ICommonTask

class ClassB : ICommonTask

The problem is that:

() => val.CommonLongRunningTask()

translates into something:

delegate
{
    return val.CommonLongRunningTask();
}

Which will fail at run time because the CommonLongRunningTask method you're binding to doesn't have a return type.

I just had this same problem and in response to the answer from Peter as to why it happens, I came up with a solution to get Task.Run from dynamic void method to work regardless.

rewrite:

() => val.CommonLongRunningTask()

as

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

with the following method:

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

The caveat being that you have to know that any CommonLongRunningTask you end up with at runtime is guaranteed to return void, otherwise you'll get another exception.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM