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

发布于 2025-02-09 11:40:41 字数 2483 浏览 4 评论 0原文

给定:

  • 游戏循环在控制台应用的主线程上运行。
  • 游戏动作/事件在其他线程上发射。

问题:

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

接下来,我将进行实施。但是我不喜欢它,原因有几个:

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

那么,有更好的方法可以做到吗?

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


}

讨论有关taskscheduler的讨论

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

}

Given:

  • Game loop is running on the main thread of console app.
  • Game actions/events are fired on other threads.

Question:

  • How can I switch/forward those actions/events to the main thread?

Next I will give my implementation. But I don't like it for several reasons:

  • My custom TaskScheduler. I'm just afraid that I might write something wrong.
  • No synchronization context.
  • Game loop ticks every N milliseconds but it would be better if it reacted to new actions/events.

So, are there a better ways to do this?

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


}

Discussion about TaskScheduler

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

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

╄→承喏 2025-02-16 11:40:41

这是带有锁定和手动Resetevent的版本,允许您等待任务。
看起来它有效,但恐怕在某些极少数情况下可能会有错误。你能告诉我我做对了吗?

讨论有关taskscheduler的讨论

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

}

Here is the version with locks and ManualResetEvent allowing you to wait for a task.
It looks like it works, but I'm afraid there might be bugs in some rare cases. Can you tell me if I did it right?

Discussion about TaskScheduler

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

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文