c等待任务-本地变量被意外更改

本文关键字:意外 变量 等待 任务 -本 | 更新日期: 2023-09-27 18:21:27

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

业务逻辑是接受入站调用,如果项还没有在全局缓存中,则处理该项;如果是,则只需更新条目,然后在稍后的代码中处理该条目。

然而,我看到了一些奇怪的行为,我无法理解。我的单元测试向QueueProcessor方法提交了两个请求(每个调用之间有2秒的延迟)。为了进行测试,我特意在QueueProcessor处理请求时调用的另一个方法中使用了task.delay。这模拟了一个真实世界的测试用例,在这个测试用例中,我们在处理第一个请求时保留了额外的请求。

我在名为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语句,该语句将刚刚处理的票证的时间戳与缓存中的时间戳进行比较。现在一切都正常。但这个新变量肯定一开始就不需要了吗?

c等待任务-本地变量被意外更改

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

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

这是一件大事。确保您完全理解这是如何工作的-这是编程的基础之一,也是理解编程的主要障碍之一。

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

你有一个tempTicketData物体,一张纸,放在冰箱里的C42盒子里。你在这张纸上写了Hi!。你的缓存对象有一张便利贴,上面写着"我的对象存储在冰箱里的C42盒子里"。现在,当你从缓存中读取票证时,你所读取的只是便利贴——只是由于C#的工作方式,这也让你可以访问real实例的所有成员。所以,当你使用像Console.WriteLine(ticket.timeStamp)这样的代码时,C#会查看便利贴,走到冰箱前,给你读论文。

现在,当你制作你的本地ticketItem时,你可以复制它——它还说"我的物品存放在冰箱里,C42盒子里"。当你更换ticketItem.timeStamp时,你要去冰箱,去C42盒子,然后更换纸张。不出所料,当有人阅读原始的便利贴并走向冰箱时,他也会看到你的零钱——只有一个tempTicketData对象。事实上,多张便利贴指向冰箱中的同一个位置并没有帮助——只有一张票。