Orleans 2.0官方文档(闫辉的个人翻译)——4.10.1 高级功能:外部任务和grain

外部的任务和grain

根据设计,从grain代码中生成的任何子任务(例如,通过使用awaitContinueWithTask.Factory.StartNew),将在与父任务相同的每个激活的TPL Task Sheduler上调度,因此继承了与grain代码的其余部分相同的单线程执行模型。这是基于grain回合制的并发性背后的要点。

在某些情况下,grain代码可能需要“突破”Orleans任务调度模型,和“做一些特殊的事情”,例如显式地将一个Task指向不同的任务调度程序或使用.NET线程池。这种情况的一个例子是,当grain代码必须执行同步远程阻塞调用(如远程IO)时。在grain环境中执行此操作,将阻塞grain以及Orleans线程之一,因此永远不应该这样做。相反,grain代码可以在线程池的线程上,执行这段阻塞代码,并且join(await)该执行的完成,然后在grain上下文中继续。我们预计,从Orleans调度程序中脱离出来,将是一种非常高级且很少需要的使用场景,超出“正常”的使用模式。

基于任务的API:

1) awaitTask.Factory.StartNewTask.ContinuewWithTask.WhenAnyTask.WhenAllTask.Delay都遵循当前的任务计划。这意味着以默认方式使用它们,无需传递不同的TaskScheduler,它们会在grain上下文中执行。

2)Task.Factory.FromAsyncTask.RunendMethod委托,都不遵循当前的任务调度程序。它们都使用TaskScheduler.Default调度程序,即.NET线程池任务调度程序。因此,Task.RunendMethod中的代码,始终运行在Orleans grain单线程执行模型外部的.NET线程池中运行,如详述。但是,await Task.Runawait Task.Factory.FromAsync之后的任何代码,在创建任务后,会回到调度程序下运行,即grain调度程序。

3)configureAwait(false)是一个显式API,用来脱离当前任务Scheduler。它将导致在等待的Task之后的代码,在TaskScheduler.Default调度程序(即.NET线程池)上执行,从而打破了Orleans grain的单线程执行模型。一般而言,您应该永远不要在grain代码中,直接使用configureAwait(false)

4)带有async void签名的方法,不应与grain一起使用。它们适用于图形用户界面事件处理程序。

示例:

下面是示例代码,演示了如何使用TaskScheduler.CurrentTask.Run以及一个特殊的自定义调度程序,以脱离Orlean grain上下文,以及如何返回到grain上下文。

   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);
   }

高级示例 - 从在线程池上运行的代码进行grain调用

更高级的场景是,一段grain的代码需要“突破”Orleans任务调度模型,并在线程池(或其他一些非Orleans上下文)上运行,但它仍然需要调用另一个grain。如果您尝试进行grain调用,但不在Orleans上下文中,则会收到一个异常,表示您“尝试在silo上发送消息,既不是从grain内发送消息,也不是在系统目标内发送消息(RuntimeContext不是设置为SchedulingContext)“。

下面的代码演示了如何从一段代码中进行grain调用,该代码在grain内部运行,但不在grain上下文中运行。

   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);
   }

库的处理

您的代码中使用的某些外部库,可能在内部使用ConfigureAwait(false)。事实上,在实现通用库时,使用ConfigureAwait(false)是.NET中一种好的、正确的实践。这在Orleans不是问题。只要调用库方法的grain代码,以常规await的方式,等待库的调用,那么grain代码就是正确的。结果将完全符合预期——库代码将在Default调度程序上运行后续代码(这恰好是ThreadPoolTaskScheduler,但它不能保证后续代码绝对会在ThreadPool的线程上运行,因为后续代码通常在前一个线程中是内联的) ,而grain代码将在Orleans的调度程序上运行。

另一个常见问题是,是否需要使用Task.Run执行库调用——也就是说,是否需要将库代码显式转卸到ThreadPool(用于执行谷物代码Task.Run(()=> myLibrary.FooAsync()))。答案是否定的。除了正在进行阻塞同步调用的库代码外,不需要将任何代码转卸到ThreadPool。通常,任何编写良好且正确的.NET异步库(返回Task并以Async后缀命名的方法)都不会进行阻塞调用。因此,除非您怀疑异步库是错误的或者有意使用同步阻塞库,否则无需将任何内容转卸到ThreadPool。

摘要

你想做什么? 怎么做
在.NET线程池线程上运行后台工作。无grain代码或grain调用。 Task.Run
grain 接口调用 方法返回类型为 TaskTask<T>
使用Orleans回合制并发保证,从grain代码运行工作任务。 Task.Factory.StartNew
执行工作项的超时时间 Task.Delay + Task.WhenAny
使用async/await 正常的.NET任务 - 异步编程模型。支持和推荐
ConfigureAwait(false) 不要在grain内部使用。仅允许在库内部。
调用异步库 await 库调用

猜你喜欢

转载自blog.csdn.net/uddiqpl/article/details/86502241