I'm using NLua to run Lua script in my app. I need to implement ability to terminate script that runs in separate thread at any time, for example user press "Stop" button and script must terminate immediately. I've read about SetDebugHook and tried to Close Lua State and call Error on state, but I always get AccessViolationException.
I've tried
Lua env = new Lua(); // created in main thread
env.DoString(); // called in second thread
// Called in main thread
public void Stop()
{
env.Close(); // Didn't work. AccessViolationException
env.State.Close(); // Didn't work. AccessViolationException
env.State.Error("err"); // Didn't work. AccessViolationException
}
Tried to synchronize threads with lock
lock (locker)
{
if (env.IsExecuting)
env.Close();
}
Same issue. AccessViolationException
Thanks.
This method works reasonably well, using the lua_sethook
to check for signal to abort before executing each line of lua code:
public partial class NluaThreading : Form
{
private Lua state;
private bool abort;
public NluaThreading()
{
InitializeComponent();
}
private void Start_Click(object sender, EventArgs e)
{
state = new Lua();
state.SetDebugHook(KeraLua.LuaHookMask.Line, 0);
state.DebugHook += State_DebugHook;
abort = true; //force abort after first debughook event
new Thread(DoLua).Start();
}
private void State_DebugHook(object sender, NLua.Event.DebugHookEventArgs e)
{
if (abort)
{
Lua l = (Lua)sender;
l.State.Error("Execution manually aborted");
}
}
private void DoLua()
{
try
{
state.DoString("while(true) do end");
}
catch (Exception e)
{
MessageBox.Show(e.Message, "DoLua", MessageBoxButtons.OK);
}
}
}
this ofcourse comes at the cost of some added overhead for every line, to reduce that you can change the hook one of the other values.
Another option is to use tokens that the lua thread would watch and then abort as needed, this method does require some handling within the lua script:
public partial class NluaThreading : Form
{
internal class Tokens
{
public bool abort = false;
}
private Lua state;
private Tokens tokens;
public NluaThreading()
{
InitializeComponent();
state = new Lua();
tokens = new Tokens();
state["tokens"] = tokens; //now the tokens are visible inside the lua
//environment and will reflect changes we make
//from the main thread
}
private void Start_Click(object sender, EventArgs e)
{
if (!state.IsExecuting)
{
tokens.abort = false;
new Thread(DoLua).Start();
}
}
private void Stop_Click(object sender, EventArgs e) => tokens.abort = true;
private void DoLua() => state.DoString("repeat print(tokens.abort) until(tokens.abort); print(tokens.abort)");
}
Now often your lua execution will be more complex, containing many nested loops, and in those cases you can implement a function in lua to check the tokens and throw an error when the token is true:
function checkTokens()
if tokens.abort then
error('Execution manually aborted')
end
end
with that loaded into the lua state we should make some changes to the DoLua
function:
private void DoLua()
{
try
{
state.DoString("while(true) do print(tokens.abort); checkTokens(); end");
}
catch(Exception e)
{
MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK);
}
}