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语句,该语句将刚刚处理的票证的时间戳与缓存中的时间戳进行比较。现在一切都正常。但这个新变量肯定一开始就不需要了吗?
tempTicketData
是一个类。在C#中,所有class
es都是引用类型。所有指向类实例的引用都指向数据的一个副本。当您更改这个通用副本时,每个人都会看到更改(最终,多线程很难)。
当您执行(tempTicketData)passInModel
时,票证不会被复制,它仍然是对同一票证的引用。因此,当你稍后修改它时,你会修改缓存中的票证,而不仅仅是本地的--本地只是对缓存中对象的引用,而不是对象的副本。
这是一件大事。确保您完全理解这是如何工作的-这是编程的基础之一,也是理解编程的主要障碍之一。
一般来说,这是一个间接性的问题。让我们想象一个简单的虚拟机,它代表您的(简化的)程序。
你有一个tempTicketData
物体,一张纸,放在冰箱里的C42盒子里。你在这张纸上写了Hi!
。你的缓存对象有一张便利贴,上面写着"我的对象存储在冰箱里的C42盒子里"。现在,当你从缓存中读取票证时,你所读取的只是便利贴——只是由于C#的工作方式,这也让你可以访问real实例的所有成员。所以,当你使用像Console.WriteLine(ticket.timeStamp)
这样的代码时,C#会查看便利贴,走到冰箱前,给你读论文。
现在,当你制作你的本地ticketItem
时,你可以复制它——它还说"我的物品存放在冰箱里,C42盒子里"。当你更换ticketItem.timeStamp
时,你要去冰箱,去C42盒子,然后更换纸张。不出所料,当有人阅读原始的便利贴并走向冰箱时,他也会看到你的零钱——只有一个tempTicketData
对象。事实上,多张便利贴指向冰箱中的同一个位置并没有帮助——只有一张票。