限制C#中的方法

时间:2010-08-13 20:26:30

标签: c# multithreading timeout

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

游戏然后调用机器人(希望并行)中的方法来处理各种事件,例如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.
}

但我认为不可能制作这样的结构。

这样做的目的是优雅地处理有意外无限循环或需要很长时间计算的AI。

解决这个问题的另一种可能方法是使用类似的东西(使用更多的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();
        }
    }
}

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

[进一步编辑]

我不太确定appdomain是什么,从它的外观我可以这样做,但是它无法看到它会如何帮助

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

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

我会读到CAS可以做什么,谢谢你们

[更多编辑]

我的头疼,我似乎无法思考或编码了。我正在建立一个消息传递系统到每个机器人的专用线程,并将暂停/睡眠这些线程

我已决定使用完全插座的服务器客户端系统。这样客户端可以做任何想做的事情,如果它拒绝回复服务器消息,我会忽略它。可惜它必须来这个。

6 个答案:

答案 0 :(得分:10)

不幸的是,没有100%安全的方式来终止线程干净地,正如您所希望的那样。

有很多方法可以尝试这样做,但它们都有一些你可能想要考虑的副作用和缺点。

唯一干净,安全且经过批准的方法是获得相关主题的合作并很好地问。但是,如果您控制代码,这只是100%保证的方式。既然你不是,那就不会。

这是问题所在。

  • 如果您执行Thread.Abort,则可能会使appdomain处于不安全状态。可能存在文件未打开,网络或数据库连接处于打开状态,内核对象处于无效状态等等。
  • 即使您将线程放入自己的应用程序域,并在中止线程后拆除appdomain,您也不能100%安全地确保您的进程将来不会出现问题。

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

假设有问题的线程经常需要调用您的库代码,以便在屏幕上绘图,或者诸如此类的东西。您可以轻松地检查这些方法,并抛出异常。

然而,这个例外可能被捕获并被吞没。

或者有问题的代码可能会进入一个无法调用你的库代码的无限循环,这会让你回到原点,如何在没有合作的情况下彻底杀死它。

我已经说过你不能。

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

以下是Joe Duffy的博客文章,其中解释了很多关于这些问题:Managed code and asynchronous exception hardening

答案 1 :(得分:4)

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

在您Task传递CancelationToken的{​​{1}}中运行委托,例如this

有一个更全面的例子here

修改

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

  1. 使用CAS限制AI,以便它无法访问线程原语或弄乱文件。

  2. 给它一个心跳回调,它在道德上有义务从长循环中调用。如果线程花费的时间过长,则此回调将抛出异常。

  3. 如果AI超时,请记录弱移动,并给它一个短暂的时间来调用该回调。如果没有,只需将线程置于睡眠状态直到下一个回合。如果它永远不会突然出现,它将继续记录弱移动,直到该进程将其作为后台线程杀死它。

  4. 利润!

答案 2 :(得分:2)

一个选择当然是自己启动一个新的Thread,而不是依赖于BeginInvoke。你可以通过Thread.Join实现超时,并且(毫不客气地)通过Thread.Abort杀死线程。

答案 3 :(得分:2)

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

也就是说,如果你限制每个机器人用CAS做什么,你可以终止线程而不用担心破坏你的系统。如果机器人卡在无限循环中,我怀疑你唯一的办法是终止线程,因为它会以编程方式永远无法访问任何检查Thread.Abort信号的代码。

This MSDN article可以为您提供有关如何使用TerminateThread函数更有效地终止失控线程的一些指导。

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

或者,如果这是一个竞争性游戏和机器人作者必须公平,你是否考虑让游戏给每个机器人一个“转弯标记”,机器人必须提出它要调用的每个动作以及何时游戏翻转,它给所有机器人一个新的令牌。这可能让你现在只需要担心失控的线程,而不是担心轮到他们的机器人。

修改

只是为人们添加一个地方Security Permission Flag Enumerations会给你一个从哪里开始的感觉。如果删除SecurityPermissionFlag.ControlThread权限(作为示例,仅授予执行代码和排除ControlThread的权限),您将删除它们的

  

能够使用某些先进的   线程上的操作。

我不知道无法进行的操作程度,但周末才能解决这个问题。

答案 4 :(得分:1)

我认为唯一的问题是倒置条件。只有在任务成功完成时才应调用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时间。

答案 5 :(得分:0)

另一个设计选项可能是允许每个机器人成为自己的进程,然后使用IPC机制来交换数据。您通常可以在没有任何可怕后果的情况下终止流程。