我正在为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,所以这也很奇怪。
你不能像一个进程那样真正地“杀死”一个线程。人们使用的模式是,在线程中运行的任何方法都需要不断检查“中止”标志,如果标志已设置,那么您的方法会逐字返回,从而停止执行。 .NET在使用'CancelToken'的任务中简化了这一点,例如,参见[这里](https://msdn.microsoft.com/en-us/library/dd997396(v = vs.110).aspx),你会请注意每次迭代*代码如何检查取消标记,如果取消标记被取消,那么它将停止执行(在这种情况下,通过抛出异常而不是返回)。 – Quantic
至于'Thread.Abort'不好,[1](http://stackoverflow.com/questions/2251964/c-sharp-thread-termination-and-thread-abort):“唯一可以保证安全的东西终止一个不合作的线程是操作系统关闭它的整个过程。“,它链接到[2](http://stackoverflow.com/questions/1559255/whats-wrong-with-using-thread-abort/1560567# 1560567):“无法保证Thread.Abort的调用实际上会中止所讨论的线程,”“简而言之,Thread.Abort最多表示设计不好,可能不可靠,而且非常危险。 – Quantic
而不是将其视为取消将其视为取消。相关阅读:https://msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx –