.net C# Spooky Action at a Distance --等待块中的值类型更改?

myzjeezk  于 11个月前  发布在  .NET
关注(0)|答案(1)|浏览(72)

我对用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");


serverEventsInFlightChanneluint的一个C#Channel
我遇到的真正令人困惑的问题是,RequestAcknowledgementEvent开头的eventId的值(该函数中的第一条日志消息)比await块中的eventId值大1(函数中的第二条日志消息)。这在结构上应该是不可能的吧?uint应该被克隆到函数中,任何人都没有办法,即使是跨线程的,也不能改变它,对吗?如果我没有弄错的话,你不能事件分发对值类型的可变引用!我假设await块与此有关?
有谁知道这是怎么发生的(以及我如何解决它)?我很困惑。

trnvg8h3

trnvg8h31#

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;
    }
}

字符串
当你到达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块在同一时间和顺序不保证上下文执行。

相关问题