dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.05k stars 4.04k forks source link

CSharpScript.EvaluateAsync() blocks until script has run to completion (or first await) #6928

Open FrankBakkerNl opened 8 years ago

FrankBakkerNl commented 8 years ago

I expected CSharpScript.EvaluateAsync(string code) to return a Task before the script has completed execution and that the task would then complete when the script has completed. This would be especially useful for potentially rong running scripts. This however is not how it currently works in version 1.1.0-rc1-20151109-01.

when I run the following

        var code = @"
System.Console.WriteLine(""Script Start"" );
System.Threading.Thread.Sleep(10000);
System.Console.WriteLine(""Script End"" );";

        var task = CSharpScript.EvaluateAsync(code);
        Console.WriteLine("EvaluateAsync Done");
        task.Wait();
        Console.WriteLine("Task Completed");
        Console.ReadLine();

It produces the output

Script Start
Script End
EvaluateAsync Done
Task Completed
khellang commented 8 years ago

Just curious; what happens if you do await Task.Delay(10000); instead of Thread.Sleep(10000);?

FrankBakkerNl commented 8 years ago

I was just trying exactly that,

        var code = @"
System.Console.WriteLine(""Script Start"" );
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine(""Script End"" );";

        var script = CSharpScript.EvaluateAsync(code);
        Console.WriteLine("EvaluateAsync Done");
        Console.WriteLine("Task Completed");
        Console.ReadLine();

This does produce the result

Script Start
EvaluateAsync Done
Task Completed
Script End

So EvaluateAsync does not execute the whole script async, it just allows you to use await inside the script. This probably makes sense, but was not what I expected

khellang commented 8 years ago

Oh, so it did work? Huh.

tenor commented 8 years ago

@FrankBakkerNl You can get your desired behavior by making Script.EvaluateAsync an inner task, similar to what the Interactive Editor does here https://github.com/dotnet/roslyn/blob/master/src/Interactive/Features/Interactive/Core/InteractiveHost.Service.cs#L816

FrankBakkerNl commented 8 years ago

@tenor: This indeed is what I ended up doing and works like expected.

I can see how this behavior is By Design, but it is somewhat confusing to have an ..Async method that does not return immediately. I think it would be good to make this clear in the final API documentation

tenor commented 8 years ago

Yes it is. To be fair the call awaits on the execution.

It seems like the default TaskScheduler causes this by inlining awaits aggressively. A custom TaskScheduler that uses a new thread per await will have the expected behavior.

FrankBakkerNl commented 8 years ago

So now I am not sure whether this means it actually is by design in which case I can of course close this issue.

tenor commented 8 years ago

I think there might be an issue, but probably not super important. The name .RunAsync suggests to me that this will always return immediately if you don't await.

@tmat Can you provide some input?

FrankBakkerNl commented 8 years ago

From another point of view, this code

    var code = @"
            System.Console.WriteLine("">Script Start"" ); 
            System.Threading.Thread.Sleep(1000);
            System.Console.WriteLine("">After Sleep"" ); 
            await System.Threading.Tasks.Task.Delay(1000);
            System.Console.WriteLine("">After await delay"" ); 
        ";

        var task1 = CSharpScript.EvaluateAsync(code);
        Console.WriteLine("Async call Done");
        task1.Wait();
        Console.WriteLine("Task Completed");

Produces the same output as when the same code is copied into a lambda.

        Func<Task> FuncAsync = async () =>
        {
            System.Console.WriteLine(">Script Start");
            System.Threading.Thread.Sleep(1000);
            System.Console.WriteLine(">After Sleep");
            await System.Threading.Tasks.Task.Delay(1000);
            System.Console.WriteLine(">After await delay");
        };

        var task2 = FuncAsync();
        Console.WriteLine("Async call Done");
        task2.Wait();
        Console.WriteLine("Task Completed");
        Console.ReadLine();

output:

>Script Start
>After Sleep
Async call Done
>After await delay
Task Completed

When looking at the script an a 'normal' async method the current behavior is exactly what is expected. When however you are not aware of the fact that the script itself can be asyc, you might expect the whole script to be run async when calling EvaluateAsync() (Like I did)

xplicit commented 8 years ago

Is there a recommended way to run the script async? I want to get Task<ScriptState> before the script completes its execution to monitor its activity, but with current implementation I don't see how can I manage it.

I want to start long-running task and monitor if it is executing or completed or threw exception. To do this I make the following:

Task<ScriptState> state = null;

ThreadPool.QueueUserWorkItem(_ => {
  state = CSharpScript.RunAsync<int>(script, opt);
  //I can get the state only when script completes execution and while script is execusing
  //state is always null 
});

//Monitor state in another place
while (!state.IsCompleted)
{
      //do something
}

Is it possible to get the Task<ScriptState> before scripts completes the execution? Script is similar to mentioned above:

 while(true)
 {
     //do something
     Thread.Sleep(1000);
 }
dickyw71 commented 5 years ago

This hasn't been updated for over two and a half years. Is is still a problem?

dkrahmer commented 4 years ago

This hasn't been updated for over two and a half years. Is is still a problem?

Yes, this is still an issue as of v3.6.0 of Microsoft.CodeAnalysis.CSharp.Scripting.

MaxenceMouchard commented 6 months ago

Still an issue 10 years later 👀

CyrusNajmabadi commented 6 months ago

@MaxenceMouchard This is on the backlog.