c#等待任务 - 意外更改局部变量

时间:2015-11-11 13:09:21

标签: c# asp.net asynchronous asp.net-web-api

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

业务逻辑是接受入站调用,如果项目尚未在全局高速缓存中处理,或者如果是,则只需更新该条目,然后在代码中稍后处理。

然而,我发现一些奇怪的行为让我无法理解。我的单元测试向我的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语句,该语句将刚刚处理的故障单的时间戳与缓存中的内容进行比较。现在一切正常。但是,首先不应该需要这个新变量吗?

1 个答案:

答案 0 :(得分:5)

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

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

这是一个非常重要的事情。确保你理解这是如何工作的完美 - 这是编程的基础之一,也是理解编程的主要障碍之一。

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

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

现在,当您制作本地ticketItem时,您复制了帖子 - 它还说“我的对象存储在冰箱中,在方框C42中”。当您更改ticketItem.timeStamp时,您将转到冰箱,装箱C42,然后更换纸张。不出所料,当有人阅读原始帖子并进入冰箱时,他也会看到你的变化 - 只有一个tempTicketData对象。多个便利贴指向冰箱中相同位置的事实无济于事 - 只有一张票。