How to control on which thread an asynchronous function is run? #509
-
I noticed that when awaiting asynchronous functions the thread which executes the script can switch. Take for instance this program which uses the task-to-promise flag: using Microsoft.ClearScript.V8;
namespace ClearScriptPlaygroud;
public class ScriptObject
{
public Task<string> GetTheString()
{
return Task.Run(() =>
{
Console.WriteLine($"{nameof(GetTheString)} {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(5 * 1000);
return "foobar";
});
}
}
internal class Program
{
static void Main()
{
using var engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableTaskPromiseConversion);
engine.AddHostObject("obj", new ScriptObject());
engine.AddHostType(typeof(Thread));
engine.AddHostType(typeof(Console));
Console.WriteLine($"Invoking on thread {Thread.CurrentThread.ManagedThreadId}");
engine.Execute("script.js", @"
function foobar() {
(async () => {
Console.WriteLine(`Async before: ${Thread.CurrentThread.ManagedThreadId}`);
const res = await obj.GetTheString();
Console.WriteLine(`Async after: ${Thread.CurrentThread.ManagedThreadId}`);
})();
}
");
engine.Invoke("foobar");
Console.ReadLine();
}
} Runnings this outputs:
Which shows that the script has switched threads. Is there a way to control which threads are used for executing the script? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 4 replies
-
Hello @handerss-tibco,
Your question really has to do with .NET task continuation handling. In principle, yes, .NET gives you control over how task continuation work items are queued and processed. If you wish to route task continuation to a given thread, you need two things:
.NET's Windows Desktop SDK includes the Note that, as written above – without an event loop – your code can't handle task continuation on the starting thread (thread 1); by the time the Let's start by making public class ScriptObject {
public async Task<string> GetTheString() {
Console.WriteLine($"{nameof(GetTheString)} {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(5 * 1000);
return "foobar";
}
} Now let's do the same for your main test code and move it into a separate method for convenience: static async Task Test() {
using var engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableTaskPromiseConversion);
engine.AddHostObject("obj", new ScriptObject());
engine.AddHostTypes(typeof(Thread), typeof(Console));
Console.WriteLine($"Invoking on thread {Thread.CurrentThread.ManagedThreadId}");
engine.Execute("script.js", @"
async function foobar() {
Console.WriteLine(`Async before: ${Thread.CurrentThread.ManagedThreadId}`);
const res = await obj.GetTheString();
Console.WriteLine(`Async after: ${Thread.CurrentThread.ManagedThreadId}`);
}
");
await (Task)engine.Invoke("foobar");
} Invoking the test method directly produces a thread switch as before: static async Task Main() {
await Test();
} Output:
But now we can use a dispatcher to get the desired behavior: static void Main() {
Dispatcher.CurrentDispatcher.BeginInvoke(new Func<Task>(async () => {
await Test();
Dispatcher.ExitAllFrames();
}));
Dispatcher.Run();
} Output:
Please let us know if you have any questions or comments. Thank you! |
Beta Was this translation helpful? Give feedback.
-
So I got a bit of a follow-up here with regards to how the In a pure managed context it seems I can easily solve this by overriding the Here's a .NET Fiddle which showcases the problem: https://dotnetfiddle.net/tqG7GL
Essentially the example boils down to that the following snippet produces different results when executed inside the script engine as javascript (same syntax) and when executed as managed code. Util.EchoPrivilege("Sync managed code before");
await Util.PerformWork();
Util.EchoPrivilege("Sync managed code after"); I'm guessing this is expected since in managed code the Is there any way to achieve a similar result in ClearScript? It seems to me that when using a |
Beta Was this translation helpful? Give feedback.
Hello @handerss-tibco,
Your question really has to do with .NET task continuation handling. In principle, yes, .NET gives you control over how task continuation work items are queued and processed.
If you wish to route task continuation to a given thread, you need two things:
SynchronizationContext
that posts task continuation callbacks to your work queue..NET's Windows Desktop SDK includes the
Dispatcher
class, which neatly satisfies both requirements (see example below). On other platforms you'll have to implem…