Saltarelle / SaltarelleCompiler

C# to JavaScript compiler – Now http://bridge.net
http://saltarelle-compiler.com
Other
297 stars 74 forks source link

Problem with async lambdas #347

Open andypennell opened 9 years ago

andypennell commented 9 years ago
    private void hello()
    {
        int aaa = Task.Run(async () => { return await AFunc(); }).GetAwaiter().GetResult();
    }

    private async Task<int> AFunc()
    {
        await Task.Delay(10);
        return 42;
    }

gives error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'int'

Something hasn't "unwrapped" the Task. Note that I already added the extra { }s to avoid a reported problem with async lambdas.

andypennell commented 9 years ago

The work-around is to add ".Result" on the end of the expression. GetResult isn't unwrapping the Task correctly, though it looks correct in the Runtime source.

erik-kallen commented 9 years ago

async () => { return await AFunc(); }) returns a Task<int>. Task.Run(async () => { return await AFunc(); }) returns a Task<Task<int>>. Task.Run(async () => { return await AFunc(); }).GetAwaiter().GetResult() returns a Task<int>.

There seems to be a Task.Run<T>(Func<Task<T>> function) in standard .net, which I didn't know existed and therefore is not in the metadata. But I don't think it would be very useful anyway.

andypennell commented 9 years ago

I am not so sure that is the cause. Try this code:

        var a = Task.Run(async () => { return await AFunc(); });
        var b = a.GetAwaiter();
        var c = b.GetResult();
        int d = c;

Compared to .NET the types of a and b are the same, but c is not so the assignment to d fails.

(FYI this pattern is a popular way to add an async call into a non-async method and block until it is done)

erik-kallen commented 9 years ago

The difference is that .net has an overload Task<T> Task.Run<T>(Func<Task<T>> function), but this overload is not in Saltarelle's custom mscorlib, so instead Task<T> Task.Run<T>(Func<T> function) will get invoked (with T being Task<int>, so the return type is Task<Task<int>>). I believe the purpose of the .net overload is to run the specified task on the threadpool synchronization context instead of on the syncronization context from which the call was made, but this need does not exist in JS (since there is only one thread anyway).

What is your reason for not just doing

var a = AFunc();
var b = a.GetAwaiter();
var c = b.GetResult();
int d = c;

?

I guess for completeness, the Run<T>(Func<Task<T>>) overload should be added, but I see it being implemented as just

[InlineCode("{func}()")]
public static Task<TResult> Run<TResult>(Func<Task<TResult>> func) {}

so it doesn't really serve any purpose except source code compatibility with .net

erik-kallen commented 9 years ago

Btw, I just saw that in the MSDN page for the overload you are using (http://msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx) it says

The Run<TResult>(Func<Task<TResult>>) method is used by language compilers to support the async and await keywords. It is not intended to be called directly from user code.

andypennell commented 9 years ago

Ah, interesting. Yes, I am attempting to port a large existing codebase, so keeping the source as compatible as possible is a requirement. This pattern is being used in catch blocks (as await cannot be done there), which I don't think is a good idea generally, so I think I will refactor this code anyway. Thank you for your prompt replies.