dynamic in Task.Run

生来就可爱ヽ(ⅴ<●) 提交于 2021-02-04 15:51:23

问题


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.


回答1:


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).




回答2:


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



回答3:


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.




回答4:


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.



来源:https://stackoverflow.com/questions/27690678/dynamic-in-task-run

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!