如何在.NET中切换到主线程(如果我使用自己的游戏循环)



给定:

  • 游戏循环正在主机应用程序的主线程上运行
  • 游戏动作/事件在其他线程上触发

问题:

  • 如何将这些操作/事件切换/转发到主线程

接下来我将给出我的实现。但我不喜欢它有几个原因:

  • 我的自定义任务调度程序。我只是担心我可能写错了什么
  • 没有同步上下文
  • 游戏循环每N毫秒一次,但如果它对新的动作/事件做出反应会更好

那么,有更好的方法吗?

class Program {

private static async Task Main(string[] args) {
var game = new Game();
new Timer( i => game.ActionAsync().Wait(), null, 0, 3000 );
new Timer( i => game.EventAsync().Wait(), null, 0, 3000 );
game.Run();
}

}
public class Game {
private GameTaskScheduler TaskScheduler { get; } = new GameTaskScheduler();

public Game() {
}

// Run game loop
public void Run() {
while (true) {
TaskScheduler.Execute();
// Game logic
Console.WriteLine( "Update: {0:00}", Environment.CurrentManagedThreadId );
Thread.Sleep( 1000 );
}
}

// User action
public async Task ActionAsync() {
await SwitchToMainThread();
Console.WriteLine( "Action: {0:00}", Environment.CurrentManagedThreadId );
}
// User event
public async Task EventAsync() {
await SwitchToMainThread();
Console.WriteLine( "Event: {0:00}", Environment.CurrentManagedThreadId );
}

// Utils
public AwaitExtensions.TaskSchedulerAwaitable SwitchToMainThread() {
return TaskScheduler.SwitchTo();
}

}

关于任务调度器的讨论

// todo: Can I do it without custom TaskScheduler?
internal class GameTaskScheduler : TaskScheduler {
private ConcurrentQueue<Task> Tasks { get; } = new ConcurrentQueue<Task>();
public override int MaximumConcurrencyLevel => 1;
public void Execute() {
while (Tasks.TryDequeue( out var task )) {
TryExecuteTask( task );
}
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) {
return false;
}
protected override void QueueTask(Task task) {
Tasks.Enqueue( task );
}
protected sealed override bool TryDequeue(Task task) {
return false;
}
protected override IEnumerable<Task>? GetScheduledTasks() {
return Tasks.ToArray();
}
}

这是带有锁和ManualResetEvent的版本,允许您等待任务。它看起来很有效,但我担心在某些罕见的情况下可能会出现错误。你能告诉我我做得对吗?

关于任务调度器的讨论

public class Game {
private GameTaskScheduler TaskScheduler { get; } = new GameTaskScheduler();

public Game() {
}

// Run game loop
public void Run() {
while (true) {
TaskScheduler.Wait();
TaskScheduler.Execute();
// Game logic
Console.WriteLine( "Update: {0:00}", Environment.CurrentManagedThreadId );
}
}

// User action
public async Task ActionAsync() {
await SwitchToMainThread(); // Switch to game thread
Console.WriteLine( "Action: {0:00}", Environment.CurrentManagedThreadId );
}
// User event
public async Task EventAsync() {
await SwitchToMainThread(); // Switch to game thread
Console.WriteLine( "Event: {0:00}", Environment.CurrentManagedThreadId );
}

// Utils
public AwaitExtensions.TaskSchedulerAwaitable SwitchToMainThread() {
return TaskScheduler.SwitchTo();
}

}
// todo: Can I do it without custom TaskScheduler?
internal class GameTaskScheduler : TaskScheduler {
public override int MaximumConcurrencyLevel => 1;
private Queue<Task> Tasks { get; } = new Queue<Task>();
private ManualResetEvent Event { get; } = new ManualResetEvent( false );

public void Wait() {
Event.WaitOne();
}
public void Execute() {
var tasks = default( Task[] );
lock (Tasks) {
tasks = Tasks.ToArray();
Tasks.Clear();
Event.Reset();
}
var exceptions = default( List<Exception> );
foreach (var task in tasks) {
try {
TryExecuteTask( task );
} catch (Exception ex) {
exceptions ??= new List<Exception>();
exceptions.Add( ex );
}
}
if (exceptions != null && exceptions.Any()) {
throw new AggregateException( exceptions );
}
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) {
return false;
}
protected override void QueueTask(Task task) {
lock (Tasks) {
Tasks.Enqueue( task );
Event.Set();
}
}
protected sealed override bool TryDequeue(Task task) {
return false;
}
protected override IEnumerable<Task> GetScheduledTasks() {
lock (Tasks) {
return Tasks.ToArray();
}
}
}

相关内容