私は簡単なテストプログラムでそれを理解したと思います
まず、次のようなNPCの基本クラスがあります。
編集:TaskCompletionSourceを使用するようにNpcBaseを更新しました:
public class NpcBase
{
// Derived classes to call this when starting an async operation
public Task BeginTask()
{
// Task already running?
if (_tcs!= null)
{
throw new InvalidOperationException("busy");
}
_tcs = new TaskCompletionSource<int>();
return _tcs.Task;
}
TaskCompletionSource<int> _tcs;
// Derived class calls this when async operation complete
public void EndTask()
{
if (_tcs != null)
{
var temp = _tcs;
_tcs = null;
temp.SetResult(0);
}
}
// Is this NPC currently busy?
public bool IsBusy
{
get
{
return _tcs != null;
}
}
}
参考までに、TaskCompletionSourceの代わりにカスタムIAsyncResult実装を使用した古いバージョンのNpcBaseを次に示します。
// DONT USE THIS, OLD VERSION FOR REFERENCE ONLY
public class NpcBase
{
// Derived classes to call this when starting an async operation
public Task BeginTask()
{
// Task already running?
if (_result != null)
{
throw new InvalidOperationException("busy");
}
// Create the async Task
return Task.Factory.FromAsync(
// begin method
(ac, o) =>
{
return _result = new Result(ac, o);
},
// End method
(r) =>
{
},
// State object
null
);
}
// Derived class calls this when async operation complete
public void EndTask()
{
if (_result != null)
{
var temp = _result;
_result = null;
temp.Finish();
}
}
// Is this NPC currently busy?
public bool IsBusy
{
get
{
return _result != null;
}
}
// Result object for the current task
private Result _result;
// Simple AsyncResult class that stores the callback and the state object
class Result : IAsyncResult
{
public Result(AsyncCallback callback, object AsyncState)
{
_callback = callback;
_state = AsyncState;
}
private AsyncCallback _callback;
private object _state;
public object AsyncState
{
get { return _state; ; }
}
public System.Threading.WaitHandle AsyncWaitHandle
{
get { throw new NotImplementedException(); }
}
public bool CompletedSynchronously
{
get { return false; }
}
public bool IsCompleted
{
get { return _finished; }
}
public void Finish()
{
_finished = true;
if (_callback != null)
_callback(this);
}
bool _finished;
}
}
次に、一次元で動くシンプルな「NPC」があります。moveTo操作が開始されると、NpcBaseのBeginTaskが呼び出されます。宛先に到着すると、EndTask()を呼び出します。
public class NpcTest : NpcBase
{
public NpcTest()
{
_position = 0;
_target = 0;
}
// Async operation to count
public Task MoveTo(int newPosition)
{
// Store new target
_target = newPosition;
return BeginTask();
}
public int Position
{
get
{
return _position;
}
}
public void onFrame()
{
if (_position == _target)
{
EndTask();
}
else if (_position < _target)
{
_position++;
}
else
{
_position--;
}
}
private int _position;
private int _target;
}
そして最後に、それを駆動するための単純なWinFormsアプリ。ボタンと2つのラベルで構成されています。ボタンをクリックすると、NPCが起動し、その位置がラベルに表示されます。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void onButtonClick(object sender, EventArgs e)
{
RunNpc1();
RunNpc2();
}
public async void RunNpc1()
{
while (true)
{
await _npc1.MoveTo(20);
await _npc1.MoveTo(10);
}
}
public async void RunNpc2()
{
while (true)
{
await _npc2.MoveTo(80);
await _npc2.MoveTo(70);
}
}
NpcTest _npc1 = new NpcTest();
NpcTest _npc2 = new NpcTest();
private void timer1_Tick(object sender, EventArgs e)
{
_npc1.onFrame();
_npc2.onFrame();
label1.Text = _npc1.Position.ToString();
label2.Text = _npc2.Position.ToString();
}
}
そしてそれは機能し、すべてがメインUIスレッドで実行されているようです...これが私が望んでいたことです。
もちろん、操作のキャンセルや例外などを処理するために修正する必要がありますが、基本的な考え方はそこにあります。