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 await back to the UI thread explicitly, for example with Control.Invoke on a control owned by that thread.

csharp
// 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));
                                  

Synchronous LoadStylesheet and Transform methods, which keep the caller on its own thread, are planned for a later release (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.