API Reference
Hosting the Engines
Which thread your code continues on after an await, for server, desktop, plugin and browser hosts
#Hosting the Engines
The XSLT and XQuery APIs are asynchronous. This page covers what that means for the thread your
code is on after an await, which depends on the kind of host.
#What the engines do
XSLT. XsltTransformer compiles and transforms on a dedicated thread with a large stack, so
deeply recursive stylesheets don't overflow the default 1 MB stack. TransformAsync has done this
since before 2.5; since 2.6.0, LoadStylesheetAsync does too. Each task therefore completes on a
thread-pool thread, not on the thread that called it.
XQuery. The query engine doesn't start threads of its own, so a query normally completes on the
calling thread. A query that calls fn:transform runs the XSLT engine, as above.
Either way, standard .NET rules decide where your code resumes after await:
-
If the calling thread has a
SynchronizationContext(an ordinary WinForms or WPF app, a Blazor component), the continuation is posted back to it, so you're on the UI thread again. -
If it has none, the continuation runs wherever the task completed. For XSLT that's a thread-pool thread.
#Servers and web apps
ASP.NET Core has no SynchronizationContext, and you don't want one: resuming on the thread pool is
what keeps request threads free. Use the async methods as they are. Pass the request's
CancellationToken, and see Resource Policy and RegexMatchTimeout when the
stylesheets or queries aren't trusted.
#Desktop applications
A WinForms or WPF app that started with Application.Run has a UI context, so after
await transformer.TransformAsync(...) you're back on the UI thread and can update controls
directly. Don't block on the task (.Result, .Wait()) from the UI thread.
#Plugins and other hosts without a UI context
Some hosts call your code from a native message loop with no SynchronizationContext: editor
plugins (Notepad++ .NET plugins, for example), COM add-ins and some game or CAD hosts. There, after
await LoadStylesheetAsync(...) or await TransformAsync(...) your code is on a thread-pool (MTA)
thread. UI calls then misbehave: an OpenFileDialog doesn't appear, and editor updates race the UI.
-
Do UI work such as file dialogs before the first
await, while you're still on the UI thread. -
Marshal UI work after an
awaitback to the UI thread explicitly, for example withControl.Invokeon a control owned by that thread.
// Still on the UI thread: ask for the input first.
string? inputPath = PickInputFile(); // OpenFileDialog
if (inputPath is null) return;
var transformer = new XsltTransformer();
await transformer.LoadStylesheetAsync(File.ReadAllText(xslPath), new Uri(xslPath));
transformer.SetSourceDocumentUri(new Uri(inputPath));
string result = await transformer.TransformAsync(File.ReadAllText(inputPath));
// Now on a thread-pool thread: hand the result back to the UI thread.
uiControl.Invoke(() => ShowResult(result));
Since 2.7.0, the simpler route is the synchronous methods. LoadStylesheet and Transform
block the calling thread and return on it, so a plugin never leaves its UI thread:
var transformer = new XsltTransformer();
transformer.LoadStylesheet(File.ReadAllText(xslPath), new Uri(xslPath));
transformer.SetSourceDocumentUri(new Uri(inputPath));
string result = transformer.Transform(File.ReadAllText(inputPath));
ShowResult(result); // still on the UI thread
The work still runs on the engine's large-stack thread, so deep recursion is as safe as with the async methods. They are not available on browser WebAssembly, which cannot block (phoenixmldb-xslt #309).
#Browser WebAssembly
Blazor WebAssembly has no threads, so the engines run inline on the browser's single thread and the continuation stays there. Since 2.6.0 XSLT works in Blazor WebAssembly again, and deep recursion there no longer exhausts the stack. A long transformation blocks the page while it runs.
#The implicit timezone (2.9.0)
A date, time or dateTime with no timezone of its own is compared, sorted, grouped and used as a key
as if it were in the implicit timezone, which is also the timezone of current-dateTime(). Unless
a host sets it, it is the timezone of the machine, so two servers in different zones can give
different answers to xs:dateTime('2020-01-01T12:00:00') lt xs:dateTime('2020-01-01T12:00:00+01:00').
Since 2.9.0 a host can set it; TimeSpan.Zero gives the same answers on every machine.
|
Setting |
Applies to |
|---|---|
|
|
Every query the engine runs. |
|
|
One execution. Set it before the query reads the current date or time. |
|
|
Each transformation the transformer runs. |
|
|
One transformation. |
|
|
The code that runs until the returned scope is disposed. |
Left at null, the default, the engines use the timezone of the machine (or, for
QueryExecutionContext and XsltTransformOptions, what the host set around the call with
ImplicitTimezone.Use), so nothing changes for a host that sets nothing. The value is a whole
number of minutes between -14:00 and +14:00; anything else is an ArgumentOutOfRangeException.
Queries and transformations that run at the same time each have their own. In XSLT, static
expressions (use-when, static variables) are evaluated when the stylesheet is compiled, in the
timezone in effect then.
var engine = new QueryEngine { ImplicitTimezone = TimeSpan.Zero };
var transformer = new XsltTransformer { ImplicitTimezone = TimeSpan.Zero };