我怎么能有两个独立的任务调度程序
本文关键字:两个 独立 任务调度程序 怎么能 | 更新日期: 2023-09-27 18:14:04
我正在编写一款游戏,并且使用OpenGL,我需要将一些工作卸载到渲染线程中,其中OpenGL上下文是活跃的,但其他一切都由正常线程池处理。
是否有一种方法可以强制任务在一个特殊的线程池中执行,并且从async
创建的任何新任务也被分派到该线程池?
我想要一些专门的线程渲染,我希望能够使用async
和await
例如创建和填充顶点缓冲区。
如果我只是使用自定义任务调度器和new Factory(new MyScheduler())
,似乎任何后续的Task
对象无论如何都会被分派到线程池,其中Task.Factory.Scheduler
突然成为null
。
下面的代码应该显示我想要能够做的事情:
public async Task Initialize()
{
// The two following tasks should run on the rendering thread pool
// They cannot run synchronously because that will cause them to fail.
this.VertexBuffer = await CreateVertexBuffer();
this.IndexBuffer = await CreateIndexBuffer();
// This should be dispatched, or run synchrounousyly, on the normal thread pool
Vertex[] vertices = CreateVertices();
// Issue task for filling vertex buffer on rendering thread pool
var fillVertexBufferTask = FillVertexBufffer(vertices, this.VertexBuffer);
// This should be dispatched, or run synchrounousyly, on the normal thread pool
short[] indices = CreateIndices();
// Wait for tasks on the rendering thread pool to complete.
await FillIndexBuffer(indices, this.IndexBuffer);
await fillVertexBufferTask; // Wait for the rendering task to complete.
}
是否有任何方法可以实现这一点,或者它超出了async
/await
的范围?
这是可能的,基本上与微软为Windows窗体和WPF同步上下文所做的相同。
第一部分 - 你在OpenGL线程中,想把一些工作放到线程池中,当这些工作完成后,你想回到OpenGL线程中。
我认为最好的方法是实现你自己的SynchronizationContext
。这个东西基本上控制TaskScheduler
如何工作以及如何安排任务。默认实现只是将任务发送到线程池。你需要做的是将任务发送到一个专用线程(保存OpenGL上下文),并在那里一个接一个地执行它们。
实现的关键是覆盖Post
和Send
方法。这两个方法都应该执行回调,其中Send
必须等待调用完成,而Post
则不需要。使用线程池的示例实现是Send
直接调用回调,Post
将回调委托给线程池。
对于您的OpenGL线程的执行队列,我认为查询BlockingCollection
的线程应该做得很好。只需将回调发送到这个队列。你可能还需要一些回调,以防你的post方法被错误的线程调用,你需要等待任务完成。
但是所有这些方法都应该有效。例如,async
/await
确保在线程池中执行异步调用后恢复SynchronizationContext
。所以你应该能够在你把一些工作放到另一个线程后返回到OpenGL线程。
第二部分 - 你在另一个线程中,想要发送一些工作到OpenGL线程并等待该工作完成。
这也是可能的。在这种情况下,我的想法是不使用Task
s,而是使用其他可等待对象。一般来说,每个对象都可以是可等待的。它只需要实现一个公共方法getAwaiter()
,该方法返回一个实现INotifyCompletion
接口的对象。await
所做的是将剩余的方法放入新的Action
中,并将此操作发送给该接口的OnCompleted
方法。一旦等待的操作完成,等待程序就会调用预定的操作。此外,该侍者还必须确保捕获SynchronizationContext
,并在捕获的SynchronizationContext
上执行延续。这听起来很复杂,但一旦你掌握了窍门,事情就变得相当简单了。对我帮助很大的是YieldAwaiter
的参考源(这基本上是如果你使用await Task.Yield()
会发生的事情)。这不是你需要的,但我认为这是一个开始的地方。
返回awaiter的方法必须负责将实际工作发送到必须执行它的线程(您可能已经从第一部分获得了执行队列),并且一旦工作完成,awaiter必须触发。
结论别搞错了。这是大量的工作。但是如果你做了所有这些,你会有更少的问题,因为你可以无缝地使用async
/await
模式,就像你将在windows窗体或WPF中工作一样,这是一个色调加。
首先,意识到await
在方法被调用后引入了特殊行为;也就是说,这段代码:
this.VertexBuffer = await CreateVertexBuffer();
与下面的代码非常相似:
var createVertexBufferTask = CreateVertexBuffer();
this.VertexBuffer = await createVertexBufferTask;
因此,您必须显式地调度代码以在不同的上下文中执行方法。
你提到使用MyScheduler
,但我没有看到你的代码使用它。像这样的代码应该可以工作:
this.factory = new TaskFactory(CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskContinuationOptions.None, new MyScheduler());
public async Task Initialize()
{
// Since you mention OpenGL, I'm assuming this method is called on the UI thread.
// Run these methods on the rendering thread pool.
this.VertexBuffer = await this.factory.StartNew(() => CreateVertexBuffer()).Unwrap();
this.IndexBuffer = await this.factory.StartNew(() => CreateIndexBuffer()).Unwrap();
// Run these methods on the normal thread pool.
Vertex[] vertices = await Task.Run(() => CreateVertices());
var fillVertexBufferTask = Task.Run(() => FillVertexBufffer(vertices, this.VertexBuffer));
short[] indices = await Task.Run(() => CreateIndices());
await Task.Run(() => FillIndexBuffer(indices, this.IndexBuffer));
// Wait for the rendering task to complete.
await fillVertexBufferTask;
}
我会考虑合并这些多个Task.Run
调用,或者(如果Initialize
是在一个正常的线程池线程上调用的)完全删除它们。