天道酬勤,学无止境

c# await task - local variables being changed unexpectedly

问题

我刚刚开始在我正在编写的一些方法中实现异步编程。

业务逻辑是接受入站调用,如果项目不在全局缓存中,则处理它,或者如果它是,则只需更新将在稍后在代码中处理的条目。

但是,我看到一些我无法理解的奇怪行为。 我的单元测试向我的 QueueProcessor 方法提交了两个请求(每次调用之间有 2 秒的延迟)。 为了测试,我有目的地在另一个方法中使用了 task.delay,QueueProcessor 在处理请求时将调用该方法。 这模拟了一个真实世界的测试用例,我们在处理第一个请求时阻止了其他请求。

我在名为 ProcessRoutine 的子方法范围内使用了一个局部变量。 但是由于某些原因,当第二次调用更新全局缓存时,仅在 ProcessRoutine 方法范围内的局部变量也发生了变化。

此外,第二个请求将仅执行更新全局缓存变量然后停止的操作逻辑。 所以没有其他代码被触发。 我已经通过我的日志确认了这一点。 我只是不明白为什么 ProessRoutine 方法中传递的数据集可以通过这种方式进行更改。

public async Task<bool> QueueProcessor(RtcPluginModel_IncidentModel passInModel)
    {


        //Ensure the processing cache is instantiated
        if (QueueGlobalVariables.processingCache == null)
        {
            QueueGlobalVariables.processingCache = new List<tempTicketData>();
        }

        try
        {



            tempTicketData ticketItem = (tempTicketData)passInModel;

            ticketItem.timeStamp = DateTime.Now;

            var checkItemExistsInProcessingCache =
                QueueGlobalVariables.processingCache.Find(
                    x => x.PAName == passInModel.PAName);


            if (checkItemExistsInProcessingCache != null)
            {

                var result = QueueGlobalVariables.processingCache.Remove( QueueGlobalVariables.processingCache.Find(
                    x => x.PAName == passInModel.PAName && x.recordId == passInModel.recordId));

                QueueGlobalVariables.processingCache.Add(ticketItem);

                logger.Trace("Stopping update branch of code as no further action needed at this point.");
            }
            else
            {

                QueueGlobalVariables.processingCache.Add(ticketItem);

                do
                {

                    var cycleTickets = QueueGlobalVariables.processingCache.Find(
                        x => x.PAName == passInModel.PAName);


                    var task = Task.Run(() => ProcessRoutineAsync(cycleTickets));

                    await task;

                } while (QueueGlobalVariables.processingCache.Find(
                    x => x.PAName == passInModel.PAName) != null);

            }
        }
        catch (Exception e)
        {
            logger.Trace("An exception has occured in the queue handler class: " + e.Message);

        }

        return true;
    } 

和:

public async Task<bool> ProcessRoutineAsync(tempTicketData passInModel)
    {

        var ticketInstance = passInModel;

         //Pass item to update routine and await response

** timestamp is fine here
        await UpdateRoutineAsync();
** timestamp changes (after second call is processed)          
        ....

var originalTimestamp = QueueGlobalVariables.processingCache.Find(
            x => x.projectAreaName == passInModel.PAName && x.workitemId == passInModel.recordId).timeStamp;


var instanceTimestamp = ticketInstance.timeStamp;

if (originalTimestamp == instanceTimestamp)
        {
            //Ticket was found in global cache and the timeindex matches

            //Remove item from cache
            var result = QueueGlobalVariables.processingCache.Remove(QueueGlobalVariables.processingCache.Find(
                x =>
                    x.projectAreaName == ticketInstance.PAName && x.recordId == ticketInstance.recordId));



        }
        return true;

    }

和:

[XmlInclude(typeof(tempTicketData))]
public class MyTicketModel
{
    public string recordId { get; set; }
    public string PAName { get; set; }
    public string ItemA { get; set; }
    public string ItbmB { get; set; }
}


[XmlInclude(typeof(tempTicketData))]
public class tempTicketData : MyTicketModel
{
    public DateTime timeStamp { get; set; }
}

**** 更新

我找到了解决问题的解决方法,但我仍然很困惑为什么它首先会发生:o/

我已经修改了 QueueProcessor,所以另一个变量专门保存时间戳:

   var ticketInstance = passInModel;
   var saveTimeIndex = ticketInstance.timeStamp;

然后,我将新变量传递给我的 if 语句,该语句将刚刚处理的票证的时间戳与缓存中的时间戳进行比较。 现在一切正常。 但肯定一开始就不需要这个新变量吗?

回答1

tempTicketData是一个类。 在 C# 中,所有的class都是引用类型。 所有指向类实例的引用都指向数据的一个副本。 当您更改此通用副本时,每个人都会看到更改(最终 - 多线程很难)。

执行(tempTicketData)passInModel时不会复制票证,它仍然是对同一张票证的引用。 因此,当您稍后修改它时,您会修改缓存中的票证,而不仅仅是您的本地 - 本地只是对缓存中对象的引用,而不是对象的副本。

这是一件大事。 确保你理解它是如何完美地工作的——它是编程的基础之一,也是理解编程的主要障碍之一。

一般来说,这是一个间接问题。 让我们想象一个代表您的(简化的)程序的简单虚拟机。

您有一个tempTicketData对象,一张纸,存储在冰箱中的 C42 框中。 在纸上,你写了Hi! . 您的缓存对象有一个便利贴,上面写着“我的对象存储在冰箱中,在 C42 框中”。 现在,当您从缓存中读取票证时,您所阅读的只是便利贴——只是由于 C# 的工作方式,这也使您可以访问真实实例的所有成员。 因此,当您使用Console.WriteLine(ticket.timeStamp)之类的代码时,C# 会查看便利贴,走到冰箱前,然后为您阅读论文。

现在,当您制作本地的ticketItem时,您复制了便利贴 - 它还显示“我的对象存储在冰箱中,在 C42 框中”。 当您更改ticketItem.timeStamp时,您将前往冰箱、C42 箱并更换纸张。 不出所料,当有人阅读原始便利贴并走到冰箱前时,他也会看到您的更改 - 只有一个tempTicketData对象。 多个便利贴指的是冰箱中的同一位置这一事实并没有帮助 - 只有一张票。

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐