0

I'm developing an online game server that uses C# for its NPC scripts. To get stateless NPCs I'm using enumerators, yielding when I have to wait for a response from the client, and calling MoveNext once I got it.

Talk()
    Msg("test");
    Select("Yes", "No");
    yield return true;
    Msg(Response);

(Simplified example, the yielding is a little more complex in reality.)

This is working fine, but async/wait would make make it cleaner, easier, and more flexible.

Talk()
    Msg("test");
    Msg(await Select("Yes", "No"));

Playing around with async/wait I've found my only problem to be that the scripts aren't as simple as sending some messages. When calling other functions from the main Talk function I have to await them, since the execution wouldn't stop otherwise. The other functions could also call even more, creating an unknown amount of awaits.

Talk()
    Msg("test");
    await OtherTalk();
    Msg("end"); // would be called right away

OtherTalk()
    Msg("test2");
    Msg(await Select("Yes", "No"));

If I close the NPC inside such a "sub talk" method I'd potentially leave quite a few tasks hanging in the air, because I wouldn't return up the chain. NPC closed, no more responses, tasks keep waiting.

The solution to this problem would be going the chain back up, with explicit checks after the await of such a function, to check if the NPC was closed somewhere down the line. But I want them to be as simple and straight forward as possible, and frankly, having an if after each call of a function would be too tedious for my taste. The server software will also be used by newbies, who might forget such a check, which could lead to problems, depending on what the script does.

My actual question now is if someone can think of a way to cancel the Tasks, without explicit checks in the scripts themselves.

Mars
  • 912
  • 1
  • 10
  • 21
  • 5
    Use the TPL's built-in cancellation features; see `CancellationTokenSource`. – SLaks Feb 20 '14 at 15:41
  • That would require checks in the scripts though, because the Tasks would return and the "parent" functions aren't supposed to continue execution. – Mars Feb 20 '14 at 19:08
  • 2
    No; awaiting a cancelled task will cancel the caller too. You just need a periodic `token.ThrowIfCancellationRequested()` call. – SLaks Feb 20 '14 at 19:17
  • Oh, misunderstood what that class does. That's just what I need! I'm now creating a token on start (private field of the script class) and pass it to the SemaphoreSlim I'm using in my Select method, that I use to wait for the response. And if I get the close NPC packet I cancel the token, which throws and gets me back to the start method. In my own Close method I'm throwing the exception myself, to quickly get out. I don't see any problems with this, and it hides everything from the scripter. If you add the answer I'll accept it :) – Mars Feb 20 '14 at 20:58

3 Answers3

4

The TPL has built-in cancellation features.

Make all of your functions accept a CanellationToken, and pass the token along when calling every async function.

Every now and then, inside the functions, call token.ThrowIfCancellationRequested(), and the entire async callchain will abort, until you handle the cancellation.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
1

You could install a custom SynchronizationContext that you can cancel. On cancellation it just would stop calling continuations. The async methods would effectively stop immediately after the next await. If nothing is holding on to those tasks they will be GC'ed and just go away.

Be aware that this way even finally blocks would not execute. using cleanups would not take place etc. Your code must be hardened to be safe under these circumstances.

usr
  • 168,620
  • 35
  • 240
  • 369
  • While it looks like I won't use this method, it was still very interesting to read up on SynchronizationContext, never heard of it (probably because I rarely do GUI programming). And it was a very interesting idea, +1. – Mars Feb 20 '14 at 21:00
0

This is a short answer.

Task cancellation must always be cooperative cancellation if you want to abort safely.

Gusdor
  • 14,001
  • 2
  • 52
  • 64