Starcounter maintains its own thread pool. Most of Starcounter API may be called only from a thread from the Starcounter thread pool. Among such APIs are: methods from Starcounter.Db class, methods from Starcounter.Request class etc. Safer to regard all APIs from Starcounter namespace as required to be called on a Starcounter thread, unless explicitly stated otherwise. Starcounter assures that an application
Main, typed JSON event handlers and HTTP-handlers registered with the
Handle API are called on a Starcounter thread. In other situations, like your code is running on separately created .Net threads or on .Net thread pool, you have to schedule a task on Starcounter thread pool using
Scheduling.RunTask in order to be able to use Starcounter API. You can call
Scheduling.RunTask while on a .NET or Starcounter thread.
StarcounterEnvironment.IsOnScheduler is another method that can be called on any thread, it tells you if the current thread is a Starcounter thread.
Example of running a background job and accessing database:
using System.Threading;using Starcounter;namespace StarcounterSampleApp{class Program{static void Main(){ThreadPool.QueueUserWorkItem(o => { RunForever(); });}static void RunForever(){while (true){Scheduling.RunTask(async () =>{await Db.TransactAsync(() =>{// Access database.});}).Wait();}}}}
Tasks scheduled by
Scheduling.RunTask as well as ready-to-call handlers make a queue of ready tasks. Then Starcounter picks tasks and runs them on a free thread from the thread pool. By default Starcounter thread pool hosts number of threads equal to number of CPU cores. It allows to maximize throughput by avoiding unnecessary context switches. But what if user code happens to block on IO or synchronization? Starcounter is able to detect such condition and launch an additional thread, so that queue processing doesn't stall. To maximize throughput, don't rely on thread pool scaling. Better to avoid blocking calls and use async IO whenever possible. To put a task in a queue, the
Scheduling.RunTask should be used:
Task RunTask(Action action)Task RunTask(Func<Task> action)
where
action is a procedure to execute on a Starcounter thread. Task being returned is a regular .Net task object which can be used in any appropriate context as any regular .Net task.
Starcounter currently doesn't provide specialized .Net synchronization context. As a consequence if you're programming asynchronous delegate and use async/await in your code, the continuation code after await gets scheduled on a regular .Net thread pool thread, not on a Starcounter thread. It means that Starcounter API can't be directly used in a continuation. Instead, one should manually schedule continuation with
Scheduling.RunTask. Below is an example demonstrating this. It uses delayed
Response. Note also the use of
Db.TransactAsync - it's needed because
Db.Transact is also a blocking call that can increase number of threads in Starcounter thread pool.
[Database]public class Person{public String Id { get; set; }}class Program{static void Main(){Handle.GET("/hello/{?}", (Request req, Int32 id) => {// As we are on Starcounter thread already, we can create a database object.// Notice that we use `Db.TransactAsync` here. Read about it in a separate article.Db.TransactAsync(() => new Person() { Id = id.ToString() });// Now we run an async task that will return the response later.DoSomethingAsync(req, id);// Indicating that HTTP response will be returned later.return HandlerStatus.Handled;});}// Async call that does some await and performs database operations after.static async void DoSomethingAsync(Request req, Int32 id) {// Now using async to simulate some long operation using await statement.await Task.Delay(10000);// Since this is not a Starcounter thread now, so we need to schedule// a Starcounter task so we can do our database operations.await Scheduling.RunTask(() => {// Querying the person.var person = Db.SQL<Person>("SELECT p FROM Person p WHERE p.Id = ?", id.ToString()).FirstOrDefault();// Doing some database operations.// Notice that we use `Db.TransactAsync` here. Read about it in a separate article.Db.TransactAsync(() => person.Id += id);});// Now in continuation we need to perform some other Starcounter operations// therefore we need to use `Scheduling.RunTask` again.await Scheduling.RunTask(() => {// Finally returning the response.Response resp = new Response() {Body = "Done with object " + id};req.SendResponse(resp);});}}
Unhandled exceptions in scheduled tasks are logged to the Administrator log. Besides of this fact, exception handling is identical to that of a regular .Net task.