我对用C#(在Godot 4平台上使用)编写重要项目相当陌生,但对一般编程并不熟悉。我有以下片段:
public async Task RequestAcknowledgementEvent(uint eventId)
{
Log($"Waiting for acknowledgement of event {eventId}");
//read from the channel until it yields an event with this id
await foreach (var e in serverEventsInFlightChannel.Reader.ReadAllAsync())
{
Log($"Wait for ack of evnt {eventId}");
Log("Got event from channel " + e);
if (e != eventId) continue;
Log($"Event {e} acked");
return;
}
}
字符串
这个函数在这里被调用:
private async Task SendCountedClientEvent(
ClientEvent clientEvent)
{
if (clientStream is null)
{
Log("Client is null", LogLevel.Error);
return;
}
var eid = Interlocked.Increment(ref _eventCounter) - 1;
clientEvent.EventId = eid;
//send event to server
GD.Print("Send event to server" + clientEvent);
clientStream.RequestStream.WriteAsync(clientEvent).Wait();
//wait for acknowledgement
await RequestAcknowledgementEvent(clientEvent.EventId);
Log("Event " + clientEvent.EventId + " acknowledged");
}
型
非常简单的东西--向服务器发送一个请求,并等待服务器在完成这个特定请求后返回(流量通过grpc流发送)。当客户端收到来自服务器的确认事件时:
case ServerEvent.EventOneofCase.Acknowledge:
var ack = serverEvent.Acknowledge!;
//see if there's any client event that server acknowledged
Log("Got acknowledge event with id " + ack.EventId);
await serverEventsInFlightChannel.Writer.WriteAsync(ack.EventId);
Log("Wrote event " + ack.EventId + " to channel");
型serverEventsInFlightChannel
是uint
的一个C#Channel
。
我遇到的真正令人困惑的问题是,RequestAcknowledgementEvent
开头的eventId
的值(该函数中的第一条日志消息)比await块中的eventId值大1(函数中的第二条日志消息)。这在结构上应该是不可能的吧?uint
应该被克隆到函数中,任何人都没有办法,即使是跨线程的,也不能改变它,对吗?如果我没有弄错的话,你不能事件分发对值类型的可变引用!我假设await块与此有关?
有谁知道这是怎么发生的(以及我如何解决它)?我很困惑。
1条答案
按热度按时间trnvg8h31#
字符串
当你到达
await foreach (var e in serverEventsInFlightChannel.Reader.ReadAllAsync())
行时,执行这个函数的线程被返回到调用函数,并且可以自由地做其他工作。如果它是UI线程,它可以继续做UI的事情,或者如果它是线程池线程,那么它被返回到线程池,可以做其他工作。所以假设有人用eventId 10调用了这个方法,所以你会得到一个日志,上面写着“Waiting for acknowledgement of event 10”,但是当它到达await时,线程被释放去做其他工作,而等待响应。当它被等待时,可能有不同的调用者调用这个方法,eventId为11,所以你得到了另一个“等待事件11的确认”,也许这就是你遇到断点的地方,所以eventId显示为11,但随后你进入调试器,认为你将进入foreach块,并期待“等待evnt 11的ack”,但实际上,11的调用线程在等待响应时被释放以做其他工作,但先前对eventId 10的调用的响应已经准备好,因此,不是在调用eventId 11的上下文中继续,而是在调用eventId 10之前的上下文中继续。您认为您仍然处于同一方法调用中,但是,即使在调试器中执行了“单步”操作,您实际上也不在同一个方法调用中。还有一点需要记住的是,当使用
await foreach
时,你不是在等待一个项目集合,你只等待一次。相反,你在等待集合中的每一个项目。每次你打开你的foreach块,你都在等待枚举中的下一个项目,所以每次循环foreach块的时候,上下文可能会混淆。当我说上下文可能会混淆时,我的意思是你可以有几个上下文都调用同一个foreach块在同一时间和顺序不保证上下文执行。