哈勃!因此,在矛的第14天,我决定开始制作用于简单在线射击的简单游戏服务器。对于分布式计算的一个话题要感动。在本周期的介绍性文章中,我想告诉大家什么是演员(在奥尔良,他们称为谷物)及其工作原理。为此,我仍然是没有Orleans的具有自制演员的简单应用程序的示例。正如他们在造船之前所说,让我们看看它如何漂浮以及普通纸船为何漂浮。有关详细信息,欢迎来猫。内容
- MS Orleans上的游戏服务器-第1部分:演员
演员们
参与者
是一个计算实体,可以响应收到的消息同时执行以下操作:向其他参与者发送有限数量的消息;创建数量有限的新演员;选择在处理下一条收到的消息时将使用的行为。不假定存在上述动作的特定顺序,并且可以并行执行所有这些动作。我们向演员发送一些消息(团队或活动),他本人也可以向其他人发送相同的消息。具有通道的协同程序以类似的原理工作。它们从通道接收一些数据,并在异步工作时向通道发送一些数据。例
实际上,两个非常简单的参与者的代码: class Program
{
interface IMessage { }
class IncrementCommand : IMessage { }
class TellCountCommand : IMessage { }
class SaidCountEvent : IMessage
{
public int Count { get; }
public int ActorId { get; }
public SaidCountEvent(int count, int actorId)
{
Count = count;
ActorId = actorId;
}
}
class WriteMessageCommand : IMessage
{
public string Message { get; }
public WriteMessageCommand(string message)
{
Message = message;
}
}
static Task CreateCounterActor(
BlockingCollection<IMessage> output,
BlockingCollection<IMessage> input,
int id
)
{
return Task.Run(() =>
{
var count = 0;
while (true)
{
var m = input.Take();
if (m is IncrementCommand)
count++;
if (m is TellCountCommand)
output.Add(new SaidCountEvent(count, id));
}
});
}
static Task CreateWriterActor(BlockingCollection<IMessage> input)
{
return Task.Run(() =>
{
while (true)
{
var m = input.Take();
if (m is WriteMessageCommand write)
Console.WriteLine(write.Message);
if (m is SaidCountEvent sc)
Console.WriteLine(
$"Counter {sc.Count} {sc.ActorId}"
);
}
});
}
static void Main(string[] args)
{
var writerInput = new BlockingCollection<IMessage>();
var firstInput = new BlockingCollection<IMessage>();
var secondInput = new BlockingCollection<IMessage>();
var writer = CreateWriterActor(writerInput);
var firstCounter = CreateCounterActor(writerInput, firstInput, 1);
var secondCounter = CreateCounterActor(writerInput, secondInput, 2);
for (int i = 0; i < 5; i++)
{
firstInput.Add(new IncrementCommand());
}
for (int i = 0; i < 9; i++)
{
secondInput.Add(new IncrementCommand());
}
firstInput.Add(new TellCountCommand());
secondInput.Add(new TellCountCommand());
writerInput.Add(new WriteMessageCommand(" Main"));
Console.ReadLine();
}
}
- 我们用来标记参与者接收或发送的消息的接口:
interface IMessage { }
- 我们告诉演员增加他的计数器的命令(内部状态):
class IncrementCommand : IMessage { }
- 在命令的帮助下,我们告诉角色将自己的当前状态(计数器)告诉其他人:
class TellCountCommand : IMessage { }
- 告诉其他演员该演员已将所有人的当前状态(计数器)告诉所有人的事件。处理TellCountCommand命令时生成:
class SaidCountEvent : IMessage
{
public int Count { get; }
public int ActorId { get; }
public SaidCountEvent(int count, int actorId)
{
Count = count;
ActorId = actorId;
}
}
计数是现在在标识符等于ActorId的actor的计数器上拨打的数量
- 此命令告诉参与者将此消息打印到控制台:
class WriteMessageCommand : IMessage
{
public string Message { get; }
public WriteMessageCommand(string message)
{
Message = message;
}
}
- 启动一个actor实例,该实例控制计数器的当前状态:
static Task CreateCounterActor(
//
BlockingCollection<IMessage> output,
//
BlockingCollection<IMessage> input,
//
int id
)
{
return Task.Run(() =>
{
var count = 0;
while (true)
{
var m = input.Take();
if (m is IncrementCommand)
count++;
if (m is TellCountCommand)
output.Add(new SaidCountEvent(count, id));
}
});
}
- 创建一个仅将消息写入控制台的参与者:
static Task CreateWriterActor(BlockingCollection<IMessage> input)
{
return Task.Run(() =>
{
while (true)
{
var m = input.Take();
if (m is WriteMessageCommand write)
Console.WriteLine(write.Message);
if (m is SaidCountEvent sc)
Console.WriteLine(
$"Counter {sc.Count} {sc.ActorId}"
);
}
});
}
static void Main(string[] args)
{
var writerInput = new BlockingCollection<IMessage>();
var firstInput = new BlockingCollection<IMessage>();
var secondInput = new BlockingCollection<IMessage>();
var writer = CreateWriterActor(writerInput);
var firstCounter = CreateCounterActor(writerInput, firstInput, 1);
var secondCounter = CreateCounterActor(writerInput, secondInput, 2);
for (int i = 0; i < 5; i++)
{
firstInput.Add(new IncrementCommand());
}
for (int i = 0; i < 9; i++)
{
secondInput.Add(new IncrementCommand());
}
firstInput.Add(new TellCountCommand());
secondInput.Add(new TellCountCommand());
writerInput.Add(new WriteMessageCommand(" Main"));
Console.ReadLine();
}
好吧,这样它绝对漂亮,看起来像奥尔良的grale(演员),让我们在柜台上做一个包装纸: public interface ICounterActor
{
void Increment();
void TellCount();
}
public class CounterActor : ICounterActor
{
private readonly Task _taks;
private readonly BlockingCollection<IMessage> _input;
private readonly BlockingCollection<IMessage> _output;
public CounterActor(BlockingCollection<IMessage> output)
{
_input = new BlockingCollection<IMessage>(); ;
_output = output;
_taks = Task.Run(() =>
{
var count = 0;
while (true)
{
var m = _input.Take();
if (m is IncrementCommand)
count++;
if (m is TellCountCommand)
_output.Add(new SaidCountEvent(count, _taks.Id));
}
});
}
public void Increment()
{
_input.Add(new IncrementCommand());
}
public void TellCount()
{
_input.Add(new TellCountCommand());
}
}
这里使用了反应模型。我们的参与者不会返回值,而只会生成具有所需值的新消息。在这里,只有一个actor处于其自己的线程中,其他所有线程都可以安全使用它,从而增加了它的计数器。另一个参与者只是在向控制台写入内容。但是,让我们想象一下,例如,每个演员为我们计算出当前正在我们的服务器上播放的十个玩家之一的当前坐标,生命值和心率。每个流都有自己的状态。我们向他人发送消息或从他人接收有关收到的损坏,使用的技能或坐标变化的消息。