我怎样才能有两个独立的任务调度程序?
How can I have two separate task schedulers?
我正在编写一个游戏,并且使用 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
的范围?
这是可能的,并且基本上与 Microsoft 为 Windows 表单和 WPF 同步上下文所做的相同。
第一部分 - 您在 OpenGL 线程中,并且想要将一些工作放入线程池中,并且在完成此工作之后您想要返回进入 OpenGL 线程。
我认为您实现此目的的最佳方式是实施您自己的 SynchronizationContext
。这个东西基本上控制了 TaskScheduler
如何工作以及它如何安排任务。默认实现只是将任务发送到线程池。你需要做的是将任务发送到一个专用线程(保存 OpenGL 上下文)并在那里一个一个地执行它们。
实现的关键是覆盖Post
和Send
方法。两种方法都应执行回调,其中 Send
必须等待调用完成,而 Post
则不需要。使用线程池的示例实现是Send
直接调用回调,Post
将回调委托给线程池
对于 OpenGL 线程的执行队列,我认为查询 BlockingCollection
的线程应该做得很好。只需将回调发送到此队列即可。您可能还需要一些回调,以防您的 post 方法从错误的线程调用并且您需要等待任务完成。
但总而言之,这种方式应该可行。例如,async
/await
确保在线程池中执行异步调用后恢复 SynchronizationContext
。因此,在将一些工作转移到另一个线程后,您应该能够 return 到 OpenGL 线程。
第二部分 - 您在另一个线程中并且想要将一些工作发送到 OpenGL 线程并等待该工作完成。
这也是可以的。在这种情况下,我的想法是您不使用 Task
s,而是使用其他 awaitable 对象。一般来说,每个对象都可以是 awaitable。它只需要实现一个 public 方法 getAwaiter()
,return 是一个实现 INotifyCompletion
interface. What await
does is that it puts the remaining method into a new Action
and sends this action to the OnCompleted
method of that interface. The awaiter is expected to call the scheduled actions once the operation it is awaiting is done. Also this awaiter has to ensure that the SynchronizationContext
is captured and the continuations are executed on the captured SynchronizationContext
. That sounds complicated, but once you get the hang of it, it goes fairly easy. What helped me a lot is the reference source of the YieldAwaiter
的对象(这基本上就是使用 await Task.Yield()
时发生的情况)。这不是您需要的,但我认为这是一个起点。
return等待者必须负责将实际工作发送到必须执行它的线程的方法(您可能已经有了第一部分的执行队列)并且等待者已经工作完成后触发。
结论
别搞错了。这是很多工作。但是如果你做了所有这些你就会有更少的问题,因为你可以无缝地使用 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
在普通线程池线程上调用)完全删除它们。
我正在编写一个游戏,并且使用 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
的范围?
这是可能的,并且基本上与 Microsoft 为 Windows 表单和 WPF 同步上下文所做的相同。
第一部分 - 您在 OpenGL 线程中,并且想要将一些工作放入线程池中,并且在完成此工作之后您想要返回进入 OpenGL 线程。
我认为您实现此目的的最佳方式是实施您自己的 SynchronizationContext
。这个东西基本上控制了 TaskScheduler
如何工作以及它如何安排任务。默认实现只是将任务发送到线程池。你需要做的是将任务发送到一个专用线程(保存 OpenGL 上下文)并在那里一个一个地执行它们。
实现的关键是覆盖Post
和Send
方法。两种方法都应执行回调,其中 Send
必须等待调用完成,而 Post
则不需要。使用线程池的示例实现是Send
直接调用回调,Post
将回调委托给线程池
对于 OpenGL 线程的执行队列,我认为查询 BlockingCollection
的线程应该做得很好。只需将回调发送到此队列即可。您可能还需要一些回调,以防您的 post 方法从错误的线程调用并且您需要等待任务完成。
但总而言之,这种方式应该可行。例如,async
/await
确保在线程池中执行异步调用后恢复 SynchronizationContext
。因此,在将一些工作转移到另一个线程后,您应该能够 return 到 OpenGL 线程。
第二部分 - 您在另一个线程中并且想要将一些工作发送到 OpenGL 线程并等待该工作完成。
这也是可以的。在这种情况下,我的想法是您不使用 Task
s,而是使用其他 awaitable 对象。一般来说,每个对象都可以是 awaitable。它只需要实现一个 public 方法 getAwaiter()
,return 是一个实现 INotifyCompletion
interface. What await
does is that it puts the remaining method into a new Action
and sends this action to the OnCompleted
method of that interface. The awaiter is expected to call the scheduled actions once the operation it is awaiting is done. Also this awaiter has to ensure that the SynchronizationContext
is captured and the continuations are executed on the captured SynchronizationContext
. That sounds complicated, but once you get the hang of it, it goes fairly easy. What helped me a lot is the reference source of the YieldAwaiter
的对象(这基本上就是使用 await Task.Yield()
时发生的情况)。这不是您需要的,但我认为这是一个起点。
return等待者必须负责将实际工作发送到必须执行它的线程的方法(您可能已经有了第一部分的执行队列)并且等待者已经工作完成后触发。
结论
别搞错了。这是很多工作。但是如果你做了所有这些你就会有更少的问题,因为你可以无缝地使用 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
在普通线程池线程上调用)完全删除它们。