Frage Zeitbegrenzung einer Methode in C #


Ich habe ein Spiel-Framework, wo es eine Liste von Bots gibt, die IBotInterface implementieren. Diese Bots werden vom Benutzer mit der einzigen Einschränkung erstellt, dass sie die Schnittstelle implementieren müssen.

Das Spiel ruft dann Methoden in den Bots (hoffentlich parallel) für verschiedene Ereignisse wie yourTurn und roundStart auf. Ich möchte, dass der Bot nur eine begrenzte Zeit damit verbringt, diese Ereignisse zu verarbeiten, bevor er gezwungen wird, den Computer zu beenden.

Ein Beispiel für die Art von Dingen, die ich versuche ist: (wo NewGame ein Delegierter ist)

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 diesem Fall muss ich EndInvoke () ausführen, das möglicherweise nicht beendet wird. Ich kann mir keinen Weg vorstellen, den Thread sauber abzubrechen.

Es wäre toll, wenn es so etwas geben würde

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

Aber ich denke nicht, dass es möglich ist, solch ein Konstrukt zu machen.

Der Zweck ist, anmutig mit KI umzugehen, die zufällige Endlosschleifen haben oder lange brauchen, um zu berechnen.

Eine andere Möglichkeit dies anzugehen, wäre etwas so zu haben (mit mehr c # und weniger 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();
        }
    }
}

Nicht der sauberste Weg, aber es würde funktionieren. (Ich werde mir Gedanken darüber machen, wie man Parameter passieren kann und später Rückgabewerte herausholt).

[Weiter]

Ich bin mir nicht sicher, was eine App-Domain ist, von dem Aussehen her könnte ich es tun, aber es kann nicht sehen, wie es helfen würde

Ich hoffe, keinen bösartigen Code zu erwarten. Der Versuch, die anderen Bots-Threads zu töten, ist kein gültiger Weg, um das Spiel zu gewinnen. Ich wollte nur jedem Bot eine Sekunde geben, um dann mit dem Spielablauf weiterzumachen, also hauptsächlich langsamen oder abgenutzten Code zu erwarten.

Ich versuche zu sehen, was ich mit Task machen kann, und komme langsam an.

Ich lese, was CAS tun kann, danke Jungs

[Mehr Bearbeiten]

Mein Kopf tut weh, ich kann nicht mehr denken oder codieren. Ich richte ein Message-Pass-System zu einem dedizierten Thread pro Bot ein und werde diese Threads aussetzen / schlafen

Ich habe beschlossen, dass ich ein vollständig gesockeltes Server-Client-System verwenden werde. Damit der Client alles machen kann, was er will, ignoriere ich einfach, wenn er nicht auf Server-Nachrichten antworten will. Schade, dass es dazu kommen musste.


13
2017-08-13 20:26


Ursprung


Antworten:


Leider gibt es keine 100% sichere Möglichkeit, einen Thread zu beenden sauber, wie Sie scheinen wollen.

Es gibt viele Möglichkeiten, wie Sie versuchen können, es zu tun, aber sie alle haben einige Nebeneffekte und Nachteile, die Sie vielleicht in Erwägung ziehen sollten.

Die einzige saubere, sichere und genehmigte Möglichkeit, dies zu tun, ist es, die Zusammenarbeit des fraglichen Threads zu bekommen und frag es schön. Dies ist jedoch nur zu 100% garantiert, wenn Sie die Kontrolle über den Code haben. Da du es nicht bist, wird es nicht sein.

Hier ist das Problem.

  • Wenn Sie ein tun Thread.AbbSie riskieren, die Appdomain in einem unsicheren Zustand zu belassen. Es könnten Dateien offen bleiben, Netzwerk- oder Datenbankverbindungen offen bleiben, Kernel-Objekte in einem ungültigen Zustand belassen usw.
  • Selbst wenn Sie den Thread in eine eigene App-Domain einfügen und die App-Domain nach dem Abbrechen des Threads abreißen, können Sie nicht 100% sicher sein, dass Ihr Prozess in Zukunft keine Probleme damit hat.

Schauen wir uns an, warum die Zusammenarbeit auch nicht zu 100% sein wird.

Nehmen wir an, der fragliche Thread muss häufig in Ihren Bibliothekscode eingreifen, um auf dem Bildschirm zu zeichnen, oder was auch immer. Sie könnten diese Methoden leicht überprüfen und eine Ausnahme auslösen.

Diese Ausnahme könnte jedoch abgefangen und verschluckt werden.

Oder der fragliche Code könnte in eine Endlosschleife geraten, die Ihren Bibliothekscode überhaupt nicht aufruft, was Sie zurück auf Platz eins bringt, wie Sie den Thread ohne seine Kooperation sauber löschen können.

Was ich schon gesagt habe, kannst du nicht.

Es gibt jedoch eine Methode, die funktionieren könnte. Du könntest den Bot in seinen eigenen Prozess bringen und dann den Prozess beenden, wenn er das Zeitlimit überschreitet. Dies würde Ihnen eine höhere Erfolgschance geben, da sich zumindest das Betriebssystem um alle Ressourcen kümmert, die es verwaltet, wenn der Prozess abbricht. Sie können natürlich den Prozess beschädigte Dateien auf dem System hinterlassen, also ist es nicht 100% sauber.

Hier ist ein Blogeintrag von Joe Duffy, der eine Menge über diese Probleme erklärt: Verwalteter Code und asynchrone Ausnahmeverhärtung.


10
2017-08-13 20:40



Ein .NET 4.0 Task gibt Ihnen erhebliche Flexibilität in Bezug auf die Stornierung.

Führen Sie den Delegaten in a aus Task dass du eine bestanden hast CancelationToken in, wie Dies.

Es gibt ein vollständigeres Beispiel Hier.

bearbeiten

Angesichts der Rückmeldungen glaube ich, dass die richtige Antwort darin besteht, diesem Plan zu folgen:

  1. Begrenzen Sie die KI mit CAS, so dass sie nicht auf Threads zugreifen oder sich mit Dateien anlegen kann.

  2. Geben Sie ihm einen Heartbeat-Callback, bei dem er aus langen Schleifen moralisch verpflichtet ist, ihn anzurufen. Dieser Callback löst eine Ausnahme aus, wenn der Thread zu lange gedauert hat.

  3. Wenn die Zeitüberschreitung der KI überschritten wird, protokollieren Sie eine schwache Bewegung und geben Sie ihr eine kurze Zeit, um diesen Rückruf aufzurufen. Wenn dies nicht der Fall ist, legst du den Thread einfach in den Schlafmodus bis zum nächsten Zug. Wenn es nie wieder herausspringt, protokolliert es weiterhin schwache Züge, bis der Prozess es als Hintergrundfaden tötet.

  4. Profitieren!


4
2017-08-13 20:32



Eine Möglichkeit ist sicherlich, ein neues zu entwickeln Faden anstatt sich auf BeginInvoke zu verlassen. Sie können das Timeout über Thread.Join implementieren und den Thread bei Bedarf ohne Probleme über Thread.Abort beenden.


2
2017-08-13 20:38



Wie ich bereits in @ Lasses Antwort erwähnt habe, solltest du wirklich darüber nachdenken, Codezugriffssicherheit zu verwenden, um zu begrenzen, was die Bots auf dem System tun dürfen. Sie können wirklich einschränken, was die Bots ausführungsmäßig tun dürfen (einschließlich der Möglichkeit, auf Threading-APIs zuzugreifen). Es könnte sich lohnen, darüber nachzudenken, jeden Bot so zu behandeln, als ob es ein Virus wäre, da du nicht kontrollierst, was er mit deinem System anstellen könnte, und ich gehe davon aus, dass dein Spiel als vollständig vertrauenswürdige Anwendung ausgeführt wird.

Das heißt, wenn Sie einschränken, was jeder Bot mit CAS macht, können Sie den Thread möglicherweise beenden, ohne Angst davor zu haben, Ihr System zu beschädigen. Wenn ein Bot in einer Endlosschleife steckenbleibt, vermute ich, dass Sie nur den Thread beenden können, da er programmatisch niemals Code erreichen kann, der nach einem Thread.Abort-Signal sucht.

Dieser MSDN-Artikel kann Ihnen eine Anleitung geben, wie Sie einen Runaway-Thread mithilfe einer TerminateThread-Funktion effektiver beenden können.

Sie müssen wirklich alle diese Dinge ausprobieren und sich selbst beweisen, was die beste Lösung sein kann.

Alternativ, wenn dies ein kompetitives Spiel ist und die Bot-Autoren fair spielen müssen, haben Sie erwogen, dem Bot einen "Turn-Token" zu geben, den der Bot mit jeder Aktion präsentieren muss, die er aufrufen will und wenn das Spiel umkippt gibt es allen Bots ein neues Token. Dadurch können Sie sich jetzt nur noch um weglaufende Threads kümmern und nicht um Roboter, die zu lange brauchen.

Bearbeiten

Nur um einen Platz für Leute zu schaffen, die nachschauen Security Permission Flag-Enumerationen gibt dir ein Gefühl dafür, wo du anfangen sollst. Wenn Sie die SecurityPermissionFlag.ControlThread-Berechtigung entfernen (als Beispiel, geben Sie dem Code nur die Berechtigung zum Ausführen und Ausschließen von ControlThread), entfernen Sie deren

Fähigkeit, bestimmte erweiterte zu verwenden   Operationen auf Threads.

Ich kenne das Ausmaß der Operationen nicht, die nicht zugänglich sind, aber es wird eine interessante Übung für das Wochenende sein, um das herauszufinden.


2
2017-08-13 21:13



Ich denke, das einzige Problem ist eine invertierte Bedingung. Du solltest anrufen EndInvoke nur wenn die Aufgabe erfolgreich abgeschlossen wurde.

Grober Vorschlag:

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;

Noch besser wäre es, einen Thread für jeden Bot explizit hochzufahren, auf diese Weise könnten Sie die Bosse, die sich schlecht benehmen, aussetzen (oder ihre Priorität herunterwählen), damit sie nicht weiterhin CPU-Zeit von den guten Bots stehlen.


1
2017-08-13 21:24



Eine weitere Möglichkeit besteht darin, jedem Bot seinen eigenen Prozess zu geben und dann einen IPC-Mechanismus für den Datenaustausch zu verwenden. Sie können einen Prozess normalerweise ohne schreckliche Auswirkungen beenden.


0
2017-08-13 21:24