如何正确中止线程?

时间:2017-04-26 17:37:36

标签: c# multithreading

我正在为NPC聊天编写服务器模拟器的脚本接口。玩家可以通过点击NPC发起NPC聊天。 NPC可以发送文本对话框。这些文本对话框还包含一个End Chat按钮,用于在脚本完成执行之前结束聊天,或者播放器可以正常继续文本对话框直到它们结束。

当玩家中断聊天时,会发送一个特殊的数据包。

我创建了一个名为WaitableResult的类,它利用ManualResetEvent阻止当前线程直到给定结果,然后返回结果:

public sealed class WaitableResult<T> where T : struct
{
    public T Value { get; private set; }

    private ManualResetEvent mEvent;

    public WaitableResult()
    {
        mEvent = new ManualResetEvent(false);
    }

    public void Wait()
    {
        mEvent.WaitOne();
    }

    public void Set(T value)
    {
        mEvent.Set();

        this.Value = value;
    }
}

这是我的脚本类:

internal sealed class NpcScript : ScriptBase
{
    public WaitableResult<bool> BoolResult { get; private set; }

    private Npc mNpc;
    private Player mPlayer;

    public NpcScript(Npc npc, Player player)
        : base(string.Format(@"..\..\scripts\npcs\{0}.lua", npc.Script), true)
    {
        mNpc = npc;
        mPlayer = player;
        mPlayer.NpcConversation = this;

        this.Expose("answer_no", false);
        this.Expose("answer_yes", true);
        this.Expose("answer_decline", false);
        this.Expose("answer_accept", true);

        this.Expose("say", new Func<string, bool>(this.Say));
        this.Expose("askYesNo", new Func<string, bool>(this.AskYesNo));
    }

    public override void Dispose()
    {
        base.Dispose();

        mPlayer.NpcConversation = null;
    }

    private bool Say(string text)
    {
        this.BoolResult = new WaitableResult<bool>();

        using (OutPacket outPacket = mNpc.GetDialogPacket(ENpcDialogType.Standard, text, 0, 0))
        {
            mPlayer.Client.SendPacket(outPacket);
        }

        this.BoolResult.Wait();

        return this.BoolResult.Value;
    }

    private bool AskYesNo(string text)
    {
        this.BoolResult = new WaitableResult<bool>();

        using (OutPacket outPacket = mNpc.GetDialogPacket(ENpcDialogType.YesNo, text))
        {
            mPlayer.Client.SendPacket(outPacket);
        }

        this.BoolResult.Wait();

        return this.BoolResult.Value;
    }
}

 public abstract class ScriptBase
{
    private string mPath;
    private Thread mThread;
    private MoonSharp.Interpreter.Script mScript;


    public ScriptBase(string path, bool useThread = false, CoreModules modules = CoreModules.None)
    {
        mPath = path;
        if (useThread) mThread = new Thread(new ThreadStart(() => mScript.DoFile(mPath)));
        mScript = new MoonSharp.Interpreter.Script(modules);
    }

    public void Execute()
    {
        if (mThread != null)
        {
            mThread.Start();
        }
        else
        {
            mScript.DoFile(mPath);
        }
    }

    public virtual void Dispose()
    {
        if (mThread != null)
        {
            mThread.Abort();
            mThread = null;
        }
    }

    protected void Expose(string key, object value)
    {
        mScript.Globals[key] = value;
    }
}

这是一个脚本的例子:

say('test')
say('some more stuff')
say('good bye')

当玩家启动与NPC的聊天并完成它而不中断它(也就是使用End Chat按钮关闭它)时,线程应该自行中止(因为它完成了所有的指示)。

然而,当玩家在完成执行之前中止聊天时,我正在调用Dispose按钮手动中止线程 - 但我不确定这是正确的方法。

每次玩家开始聊天时,我的内存使用量也会增加1 MB,所以这也很奇怪。

1 个答案:

答案 0 :(得分:0)

由于 Quantic 已经解决,通过Thread.Abort中止线程会引发意外 - 甚至是危险的结果。 Quantic 还解决了处理线程的首选模式,通常是线程循环并检查终止标志,如果您正在执行任意Lua脚本,这是不可能的。

您可以选择创建简约调试器并将其附加到脚本中。连接此调试器后,将为正在运行的脚本中的每条指令调用IDebugger.GetAction()方法。 如果在GetAction()来电期间你提出了Exception,则调试程序将彻底终止该脚本。

Here is an example使用这种技术停止长时间运行的脚本的标记,简约调试器。如您所见,大多数实现都是空的。在您的情况下,您可以只使用一个布尔标志来指示必须终止脚本,而不是使用正在运行的指令计数器。

public DebuggerAction GetAction(int ip, SourceRef sourceref)
{
    if( _abortScript )          
       throw new MyException();  // abort cleanly

    // Proceed running the next statement
    return new DebuggerAction() { 
      Action = DebuggerAction.ActionType.StepIn,
    };
}

关于您遇到的内存增加,这是手动生成线程each thread initializes its own stack space and on a 32-bit process, the default is precisely 1MB的一个不便之处。有Thread constructor overload允许您自定义堆栈大小;根据脚本的复杂程度,可以将此值设置为较小的值,而不会有堆栈溢出情况的风险。最小化内存使用的另一个选择是使用Task.Run而不是生成自己的线程(这使用了线程池),但是不使用Thread.Abort强制,你不想去杀死汇集的线程!