C# 中方法的时间限制

发布于 2024-09-14 10:29:41 字数 2042 浏览 3 评论 0原文

我有一个游戏框架,其中有一个实现 IBotInterface 的机器人列表。这些机器人是由用户定制的,唯一的限制是它们必须实现接口。

然后游戏会调用机器人中的方法(希望是并行的)来处理各种事件,例如 yourTurn 和 roundStart。我希望机器人在被迫退出计算之前只花费有限的时间来处理这些事件。

我正在尝试的一个例子是:(其中 NewGame 是委托)

Parallel.ForEach(Bots, delegate(IBot bot)
                {
                    NewGame del = bot.NewGame;
                    IAsyncResult r = del.BeginInvoke(Info, null, null);
                    WaitHandle h = r.AsyncWaitHandle;
                    h.WaitOne(RoundLimit);
                    if (!r.IsCompleted)
                    {
                        del.EndInvoke(r);
                    }
                }
            );

在这种情况下,我被迫运行 EndInvoke() ,它可能不会终止。我想不出一种方法来干净地中止线程。

如果有某种形式那就太好了

try { 
 bot.NewGame(Info);
} catch (TimeOutException) {
 // Tell bot off.
} finally {
 // Compute things.
}

,但我认为不可能做出这样的构造。

这样做的目的是优雅地处理那些意外出现无限循环或需要很长时间计算的人工智能。

解决这个问题的另一种可能的方法是使用类似的方法(使用更多的 c# 和更少的伪代码),

Class ActionThread {
    pulbic Thread thread { get; set; }
    public Queue<Action> queue { get; set; }

    public void Run() {
        while (true) {
            queue.WaitOne();
            Act a = queue.dequeue();
            a();
        }
    }

Class foo {
    main() {
        ....
        foreach(Bot b in Bots) {
            ActionThread a = getActionThread(b.UniqueID);
            NewGame del = b.NewGame;
            a.queue.queue(del);
        }
        Thread.Sleep(1000);
        foreach (ActionThread a in Threads) {
            a.Suspend();
        }
    }
}

这不是最干净的方法,但它会起作用。 (稍后我会担心如何传入参数并取出返回值)。

[进一步编辑]

我不太确定应用程序域是什么,从它的外观来看我可以这样做,但它看不出它有什么帮助

我希望不要期待恶意代码。试图杀死其他机器人线程并不是赢得游戏的有效方法。我只是想给每个机器人一秒钟的时间来计算,然后继续游戏流程,所以主要期待这里的代码缓慢或有错误。

我正在尝试看看我能用 Task 做什么,慢慢地到达某个地方。

我会读一下 CAS 能做什么,谢谢你们

[更多编辑]

我头疼,我似乎无法再思考或编码了。我正在为每个机器人的专用线程设置一个消息传递系统,并将挂起/睡眠这些线程,

我决定使用完全套接字的服务器客户端系统。这样客户端就可以做任何它想做的事情,如果它拒绝回复服务器消息,我将忽略它。可惜事情发展到了这个地步。

I have a Game framework where there is a list of Bots who implement IBotInterface. These bots are custom made by user with the only limitation that they must implement the interface.

The game then calls methods in the bots (hopefully in parallel) for various events like yourTurn and roundStart. I want the bot to only spend a limited amount of time handling these events before being forced to quit computing.

An example of the kind of thing i'm trying is : (where NewGame is a delegate)

Parallel.ForEach(Bots, delegate(IBot bot)
                {
                    NewGame del = bot.NewGame;
                    IAsyncResult r = del.BeginInvoke(Info, null, null);
                    WaitHandle h = r.AsyncWaitHandle;
                    h.WaitOne(RoundLimit);
                    if (!r.IsCompleted)
                    {
                        del.EndInvoke(r);
                    }
                }
            );

In this case I am forced to run EndInvoke() which might not terminate. I cant think of a way to cleanly abort the thread.

It would be great if there was some kind of

try { 
 bot.NewGame(Info);
} catch (TimeOutException) {
 // Tell bot off.
} finally {
 // Compute things.
}

But I dont think its possible to make such a construct.

The purpose of this is to gracefully handle AI's who have accidental infinite loops or are taking a long time to compute.

Another possible way of tackling this would be to have something like this (with more c# and less pseudocode)

Class ActionThread {
    pulbic Thread thread { get; set; }
    public Queue<Action> queue { get; set; }

    public void Run() {
        while (true) {
            queue.WaitOne();
            Act a = queue.dequeue();
            a();
        }
    }

Class foo {
    main() {
        ....
        foreach(Bot b in Bots) {
            ActionThread a = getActionThread(b.UniqueID);
            NewGame del = b.NewGame;
            a.queue.queue(del);
        }
        Thread.Sleep(1000);
        foreach (ActionThread a in Threads) {
            a.Suspend();
        }
    }
}

Not the cleanest way but it would work. (I'll worry about how to pass paramaters in and take return values out later).

[Further Edit]

I'm not too sure what an appdomain is, from the looks of it I could do so, but it cant see how it would help

I hope not to expect malicious code. Trying to kill the other bots threads is not a valid way to win the game. I just wanted to give every bot a second to compute then move on with the gameflow so mainly expecting slow or bugged code here.

I'm trying to see what I can do with Task, getting somewhere slowly.

I'll read into what CAS can do, thank you guys

[More Edit]

My head hurts, I can't seem to think or code anymore. I'm setting up a message pass system to a dedicated thread per bot and will suspend/sleep those threads

I have decided that I will use a fully socketed server client system. So that the client can do whatever it wants and I'll just ignore if it refuses to reply to server messages. Pity it had to come to this.

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

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

发布评论

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

评论(6

反差帅 2024-09-21 10:29:41

不幸的是,没有 100% 安全的方法可以干净地终止线程,正如您似乎想要的那样。

您可以尝试多种方法,但它们都有一些您可能需要考虑的副作用和缺点。

唯一干净、安全且经过批准的方法是获得相关线程的合作,并好好地询问。然而,如果您能控制代码,这只是 100% 有保证的方法。既然你不是,那就不会。

问题就在这里。

  • 如果您执行 Thread.Abort,您可能会面临将应用程序域留在不安全状态。可能有文件保持打开状态、网络或数据库连接保持打开状态、内核对象处于无效状态等。
  • 即使您将线程放入其自己的应用程序域中,并在中止线程后拆除应用程序域,您也无法100% 安全,您的流程将来不会因此而出现问题。

让我们看看为什么合作也不会是 100%。

假设有问题的线程经常需要调用您的库代码,以便在屏幕上绘图或执行其他操作。您可以轻松地检查这些方法,并引发异常。

然而,这个异常可能会被捕获并被吞掉。

或者,有问题的代码可能会进入无限循环,根本不调用您的库代码,这会让您回到第一个方向,如何在不合作的情况下干净地杀死线程。

我已经说过你不能。

然而,有一种方法可能有效。您可以将机器人生成到自己的进程中,然后在超时时终止该进程。这将为您提供更高的成功机会,因为至少操作系统会在进程终止时处理它管理的所有资源。当然,您可以让该进程在系统上留下损坏的文件,因此,它并不是 100% 干净的。

以下是 Joe Duffy 的博客文章,其中详细解释了这些问题: 托管代码和异步异常强化

Unfortunately there is no 100% safe way to terminate a thread cleanly, as you seem to want.

There are many ways you can try to do it though, but they all have some sideeffects and downsides that you might want to consider.

The only clean, safe, and approved, way to do this is to get the cooperation of the thread in question and ask it nicely. However, this is only a 100% guaranteed way if you're in control of the code. Since you're not, it won't be.

Here's the problem.

  • If you do a Thread.Abort, you risk leaving the appdomain in an unsafe state. There could be files left open, network or database connections left open, kernel objects left in an invalid state, etc.
  • Even if you place the thread into its own appdomain, and tear down the appdomain after aborting the thread, you can't be 100% safe that your process won't have problems in the future because of it.

Let's look at why cooperation isn't going to be 100% either.

Let's say the thread in question often needs to call into your library code, in order to draw on screen, or whatnot. You could easily place a check into those methods, and throw an exception.

However, that exception could be caught, and swallowed.

Or the code in question could go into an infinite loop that doesn't call your library code at all, which leaves you back to square one, how to cleanly kill the thread without its cooperation.

Which I already said you can't.

There is, however, one method that might work. You could spawn the bot into its own process, then kill the process when it times out. This would give you a higher chance of success because at least the operating system will take care of all resources it manages when the process dies. You can of course have the process leave corrupted files on the system, so again, it's not 100% clean.

Here's a blog entry by Joe Duffy that explains a lot about these problems: Managed code and asynchronous exception hardening.

沉溺在你眼里的海 2024-09-21 10:29:41

.NET 4.0 Task 为您提供了取消方面的相当大的灵活性。

在您已将 CancelationToken 传递到的 Task 中运行委托,例如 这个

这里有一个更完整的示例

编辑

根据反馈,我相信正确的答案是遵循这个计划:

  1. 限制AI使用CAS,这样它就不能访问线程原语或弄乱文件。< /p>

  2. 给它一个心跳回调,它在道德上有义务从长循环内部调用。如果线程花费的时间太长,此回调将引发异常。

  3. 如果 AI 超时,请为其记录一个弱动作,并给它一小段时间来调用该回调。如果没有,只需让线程进入休眠状态,直到下一轮。如果它永远无法摆脱困境,它将继续记录弱动作,直到进程因为它是后台线程而将其杀死。

  4. 利润!

A .NET 4.0 Task gives you considerably flexibility with regard to cancelling.

Run the delegate in a Task that you've passed a CancelationToken into, like this.

There's a fuller example here.

edit

In light of the feedback, I believe that the right answer is to follow this plan:

  1. Limit the AI using CAS, so that it can't access threading primitives or mess with files.

  2. Give it a heartbeat callback that it is morally obligated to call from inside long loops. This callback will throw an exception if the thread has taken too long.

  3. If the AI times out, log a weak move for it, and give it a short amount of time to call that callback. If it doesn't, just put the thread to sleep until its next turn. If it never does snap out of it, it will keep logging weak moves until the process kills it for being a background thread.

  4. Profit!

呢古 2024-09-21 10:29:41

一种选择当然是自己启动一个新的线程,而不是依赖 BeginInvoke。您可以通过 Thread.Join 实现超时,并在必要时通过 Thread.Abort(毫不客气地)终止线程。

One option is certainly to spin up a new Thread yourself, rather than relying on BeginInvoke. You can implement the timeout via Thread.Join and (unceremoniously) kill the thread, if necessary, via Thread.Abort.

谈下烟灰 2024-09-21 10:29:41

正如我在 @Lasse 的回答中提到的,您确实应该考虑使用代码访问安全来限制机器人在系统上执行的操作。您确实可以限制机器人可以执行的操作(包括删除它们访问线程 API 的能力)。可能值得考虑将每个机器人视为病毒,因为您无法控制它对您的系统执行的操作,并且我假设您的游戏将作为完全信任的应用程序运行。

也就是说,如果您限制每个机器人使用 CAS 执行的操作,您也许能够终止线程,而不必担心损坏您的系统。如果机器人陷入无限循环,我怀疑您唯一的办法就是终止线程,因为以编程方式,它将永远无法到达任何检查 Thread.Abort 信号的代码。

这篇 MSDN 文章 可能会为您提供一些有关如何更有效地终止失控线程的指导终止线程函数。

你真的必须尝试所有这些事情并向自己证明这可能是最好的解决方案。

或者,如果这是一款竞技游戏并且机器人作者必须公平竞争,您是否考虑过让游戏为每个机器人提供一个“回合令牌”,机器人必须在它想要调用的每个动作以及游戏翻转时呈现该“回合令牌” ,它给所有机器人一个新的令牌。这可能让您现在只需要担心失控的线程,而不用担心轮到时间太长的机器人。

编辑

只是为了添加一个地方供人们查看安全权限标志枚举会让您了解从哪里开始。如果您删除 SecurityPermissionFlag.ControlThread 权限(例如,仅授予代码执行权限并排除 ControlThread),您将删除它们

能够使用某些高级功能
线程上的操作。

我不知道无法访问的操作的范围,但周末弄清楚这一点将是一个有趣的练习。

As I mentioned on @Lasse's answer, you really should consider using Code Access Security to limit what the bots are allowed to do on the system. You can really restrict what the bots are allowed to do execution wise (including removing their ability to access threading APIs). It might be worth considering treating each bot as if it was a virus since you aren't in control of what it could do to your system and I'm assuming that your game will be running as a full-trust application.

That said, if you restrict what each bot does with CAS, you may be able to terminate the thread without fear of corrupting your system. If a bot is stuck in an infinite loop, I suspect that your only recourse will be to terminate the thread since it will, programatically, never be able to reach any code that would check for a Thread.Abort signal.

This MSDN article may give you some guidance on how to more effectively terminate a run-away thread using a TerminateThread function.

You really have to try all of these things out and prove to yourself which may be the best solution.

Alternatively, if this a competitive game and the bot authors have to play fair, have you considered having the game give each bot a "turn token" that the bot has to present with each action that it wants to invoke and when the game flips turns, it gives all the bots a new token. This may allow you to now only have to worry about run-away threads and not robots who take to long on their turn.

Edit

Just to add a place for people to go look Security Permission Flag Enumerations will give you a sense of where to start. If you remove the SecurityPermissionFlag.ControlThread permission (as an example, only giving the code permission to execute and excluding ControlThread) you will remove their

Ability to use certain advanced
operations on threads.

I don't know the extent of the operations that are inaccessible, but it will be an interesting exercise for the weekend to figure that out.

鸠书 2024-09-21 10:29:41

我认为唯一的问题是条件倒置。仅当任务成功完成时,您才应调用 EndInvoke

粗略建议:

var goodBots = new List<IBot>();
var results = new IAsyncResult[Bots.Count];
var events = new WaitHandle[Bots.Count];
int i = 0;
foreach (IBot bot in Bots) {
    NewGame del = bot.NewGame;
    results[i] = del.BeginInvoke(Info, null, null);
    events[i++] = r.AsyncWaitHandle;
}
WaitAll(events, RoundLimit);
var goodBots = new List<IBot>();
for( i = 0; i < events.Count; i++ ) {
    if (results[i].IsCompleted) {
        goodBots.Add(Bots[i]);
        EndInvoke(results[i]);
        results[i].Dispose();
    }
    else {
        WriteLine("bot " + i.ToString() + " eliminated by timeout.");
    }
}
Bots = goodBots;

更好的方法是显式地为每个机器人启动一个线程,这样您就可以暂停行为不当的机器人(或降低其优先级),这样它们就不会继续从良好的机器人那里窃取 CPU 时间。

I think the only problem is an inverted conditional. You should call EndInvoke only if the task completed successfully.

Rough suggestion:

var goodBots = new List<IBot>();
var results = new IAsyncResult[Bots.Count];
var events = new WaitHandle[Bots.Count];
int i = 0;
foreach (IBot bot in Bots) {
    NewGame del = bot.NewGame;
    results[i] = del.BeginInvoke(Info, null, null);
    events[i++] = r.AsyncWaitHandle;
}
WaitAll(events, RoundLimit);
var goodBots = new List<IBot>();
for( i = 0; i < events.Count; i++ ) {
    if (results[i].IsCompleted) {
        goodBots.Add(Bots[i]);
        EndInvoke(results[i]);
        results[i].Dispose();
    }
    else {
        WriteLine("bot " + i.ToString() + " eliminated by timeout.");
    }
}
Bots = goodBots;

Even better would be to explicitly spin up a thread for each bot, that way you could suspend the misbehaving bots (or dial down their priority) so that they don't continue to steal CPU time from the good bots.

是你 2024-09-21 10:29:41

另一种设计方案可能是允许每个机器人成为自己的进程,然后使用 IPC 机制来交换数据。您通常可以终止进程而不会产生任何可怕的影响。

another design option may be to allow each bot to be its own process, then use an IPC mechanism to exchange data. You can usually terminate a process without any terrible repercussions.

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