Show / Hide Table of Contents
    Important

    You are looking at the 1.5 documentation

    Orleans 2.0 is a significant overhaul from the 1.x versions. You can find 2.0 documentation here.

    External Tasks and Grains

    By design, any sub-Tasks spawned from grain code (for example, by using await or ContinueWith or Task.Factory.StartNew) will be dispatched on the same per-activation TPL Task Scheduler as the parent task and therefore inherit the same single-threaded execution model as the rest of grain code. This is the main point behind single threaded execution of grain turn based concurency.

    In some cases grain code might need to “break out” of the Orleans task scheduling model and “do something special”, such as explicitly pointing a Task to a different task scheduler or using the .NET Thread pool. An example of such cases is when grain code has to execute a synchronous remote blocking call (such as remote IO). Doing that in the grain context will block the grain as well as one of the Orleans threads and thus should never be made. Instead, the grain code can execute this piece of blocking code on the thread pool thread and join (await) the completion of that execution and proceed in the grain context. We expect that escaping from the Orleans scheduler will be a very advanced and seldom required usage scenario beyond the “normal” usage patterns.

    Task based APIs:

    1) await, Task.Factory.StartNew, Task.ContinuewWith, Task.WhenAny, Task.WhenAll, Task.Delay all respect the current Task Scheduler. That means that using them in the default way, without passing a different TaskScheduler, will cause them to execute in the grain context.

    2) Both Task.Run and the endMethod delegate of Task.Factory.FromAsync do NOT respect the current task Scheduler. They both use the TaskScheduler.Default scheduler, which is the .NET thread pool task Scheduler. Therefore, the code inside Task.Run and the endMethod will ALWAYS run on the .NET thread pool outside of the single-threaded execution model for Orleans grains, as detailed here. However, any code after the await Task.Run or await Task.Factory.FromAsync will run back under the scheduler at the point the task was created, which is the grain scheduler.

    3) configureAwait(false) is an explicit API to escape the current task Scheduler. It will cause the code after an awaited Task to be executed on the TaskScheduler.Default scheduler, which is the .NET thread pool, and will thus break the single-threaded execution of the Orleans grain. You should in general never ever use configureAwait(false) directly in grain code.

    4) Methods with signature async void should not be used with grains. They are intended for graphical user interface event handlers.

    Example:

    Below is sample code that demonstrates the usage of TaskScheduler.Current, Task.Run and a special custom scheduler to escape from Orlean grain context and how to get back to it.

       public async Task MyGrainMethod()
       {
            // Grab the Orleans task scheduler
            var orleansTs = TaskScheduler.Current;
            await TaskDelay(10000);
            // Current task scheduler did not change, the code after await is still running in the same task scheduler.
            Assert.AreEqual(orleansTs, TaskScheduler.Current);
    
            Task t1 = Task.Run( () =>
            {
                 // This code runs on the thread pool scheduler, not on Orleans task scheduler
                 Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
                 Assert.AreEqual(TaskScheduler.Default, TaskScheduler.Current);
            } );
            await t1;
            // We are back to the Orleans task scheduler. 
            // Since await was executed in Orleans task scheduler context, we are now back to that context.
            Assert.AreEqual(orleansTS, TaskScheduler.Current);
    
            // Example of using ask.Factory.StartNew with a custom scheduler to escape from the Orleans scheduler
            Task t2 = Task.Factory.StartNew(() =>
            {
                 // This code runs on the MyCustomSchedulerThatIWroteMyself scheduler, not on the Orleans task scheduler
                 Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
                 Assert.AreEqual(MyCustomSchedulerThatIWroteMyself, TaskScheduler.Current);
            },
            CancellationToken.None, TaskCreationOptions.None,
            scheduler: MyCustomSchedulerThatIWroteMyself);
            await t2;
            // We are back to Orleans task scheduler.
            Assert.AreEqual(orleansTS, TaskScheduler.Current);
       }
    

    Advanced Example - making a grain call from code that runs on a thread pool

    An even more advanced scenario is a piece of grain code that needs to “break out” of the Orleans task scheduling model and run on a thread pool (or some other, non-Orleans context), but still needs to call another grain. If you try to make a grain call but are not within an Orleans context, you will get an exception that says you are "trying to send a message on a silo not from within a grain and not from within a system target (RuntimeContext is not set to SchedulingContext)".

    Below is code that demonstrates how a grain call can be made from a piece of code that runs inside a grain but not in the grain context.

       public async Task MyGrainMethod()
       {
            // Grab the Orleans task scheduler
            var orleansTs = TaskScheduler.Current;
            Task<int> t1 = Task.Run(async () =>
            {
                 // This code runs on the thread pool scheduler, not on Orleans task scheduler
                 Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
                 // You can do whatever you need to do here. Now let's say you need to make a grain call.
                 Task<Task<int>> t2 = Task.Factory.StartNew(() =>
                 {
                    // This code runs on the Orleans task scheduler since we specified the scheduler: orleansTs.
                    Assert.AreEqual(orleansTS, TaskScheduler.Current);
                    return GrainFactory.GetGrain<IFooGrain>(0).MakeGrainCall();
                 }, CancellationToken.None, TaskCreationOptions.None, scheduler: orleansTs);
    
                 int res = await (await t2); // double await, unrelated to Orleans, just part of TPL APIs.
                 // This code runs back on the thread pool scheduler, not on the Orleans task scheduler
                 Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
                 return res;
            } );
    
            int result = await t1;
            // We are back to the Orleans task scheduler.
            // Since await was executed in the Orleans task scheduler context, we are now back to that context.
            Assert.AreEqual(orleansTS, TaskScheduler.Current);
       }
    

    Dealing with libraries

    Some external libraries that your code is using might be using ConfigureAwait(false) internally. In fact, it is a good and correct practice in .NET to use ConfigureAwait(false) when implementing general purpose libraries. This is not a problem in Orleans. As long as the code in the grain that invokes the library method is awaiting the library call with a regular await, the grain code is correct. The result will be exactly as desired -- the library code will run continuations on the Default scheduler (which happens to be ThreadPoolTaskScheduler but it does not guarantee that the continuations will definitely run on a ThreadPool thread, as continuations are often inlined in the previous thread), while the grain code will run on the Orleans scheduler.

    Another frequently-asked question is whether there is a need to execute library calls with Task.Run -- that is, whether there is a need to explicitly offload the library code to ThreadPool (for grain code to do Task.Run(()=> myLibrary.FooAsync())). The answer is No. There is no need to offload any code to ThreadPool, except for the case of library code that is making a blocking synchronous calls. Usually, any well-written and correct .NET async library (methods that return Task and are named with an Async suffix) do not make blocking calls. Thus there is no need to offload anything to ThreadPool, unless you suspect the async library is buggy or if you are deliberately using a synchronous blocking library.

    Summary

    What are you trying to do? How to do it
    Run background work on .NET thread-pool threads. No grain code or grain calls allowed. Task.Run
    Grain interface call Method return types = Task or Task<T>
    Run worker task from grain code with Orleans turn-based concurrency guarantees. Task.Factory.StartNew
    Timeouts for executing work items Task.Delay + Task.WhenAny
    Use with async/await The normal .NET Task-Async programming model. Supported & recommended
    ConfigureAwait(false) Do not use inside grain code. Allowed only inside libraries.
    Calling async library await the library call
    • Improve this Doc
    Back to top Generated by DocFX