在并行运行 2 个或更多任务时保留变量值

Posted

技术标签:

【中文标题】在并行运行 2 个或更多任务时保留变量值【英文标题】:Retain variable values while running 2 or more tasks in parallel 【发布时间】:2020-03-02 16:32:48 【问题描述】:

我正在使用计时器,并已将线程作为解决问题的第一步。我必须每 30 秒发送一次参考,每次单击按钮时都会创建此参考。所以我有以下代码:

public MyReference SomeReference get;set;
public string MyFullName get;set; //this is bound to WPF textbox

public bool Saved()

     SomeReference.Id = GeneratedId;
     SomeReference.FullName = MyFullName;

     return SavedtoDB(SomeReference);


public void OnButtonClick()

    if(Saved()) //timer should only start if it is already saved in the database
         var t = new Thread(() => ExecuteTimer(SomeReference));


public void ExecuteTimer(MyReference someReference)

    System.Timers.Timer sendTimer = new System.Timers.Timer(30000);
    sendTimer.Elapsed += (sender, e) => ExecuteElapsed(sender, e, someReference, sendTimer);
    sendTimer.Start();


public void ExecuteElapsed(object source, ElapsedEventArgs e, MyReference someReference, System.Timers.Timer sendtimer)

    sendtimer.Stop();
    Send(someReference);

以下是 MyReference 类:

public class MyReference()

    public string Id get;set;
    public string FullName get;set;

修改问题重点:

问题在于它只发送最新的值someReference 而忽略以前的值。如何在 30 秒后发送每个值而不用 someReference 的最新值替换它们?

【问题讨论】:

你为什么要在计时器过去后停止?这不会使计时器本身过时,因为创建的每个计时器只运行一次,直到它被停止? System.Timers.Timer 已经在不同的线程上运行。此处发布的代码创建 多个 单次触发计时器,所有这些计时器都保持活动状态,从而导致泄漏。你想做什么?您想解决的真正问题是什么?为什么不使用例如Task.Delay 为按钮操作添加延迟?如果您想在每次点击时重置计时器,您可以停止并重新启动它 @RobinB 我正在停止它,这样它只会过去一次。如果它不停止,它将在 30 秒后再次执行 elapse。 这里唯一可以确定的是不需要线程。 你改变了Saved中的对象。创建一个新的,一切都会奏效。 SomeReference = new MyReference(); 作为Saved 的第一行。 【参考方案1】:

您的对象似乎需要deep clone。

让我解释一下为什么对您的对象的引用是不够的。 SomeReference 变量将始终指向您的 MyReference 实例所在的内存位置。如果您单击按钮,则会写入/覆盖此数据。当您的计时器触发时,它将始终加载位于 SomeReference 的任何数据并发送。

实施深拷贝会将您的实例物理复制到内存中,这样您就可以保留对象的副本,该副本可用于发送而不会被您的Save() 调用覆盖。

要实现这一点,您可以(还有很多其他方法)使用ICloneable 接口,如下所示:

  class MyReference : ICloneable
  
    public string Id  get; set; 
    public string FullName  get; set; 

    public object Clone()
    
      return new MyReference()
      
        Id = this.Id,
        FullName = this.FullName
      ;
    
  

现在单击按钮时,您可以使用 Theodors Task.Delay 方法并为其提供对象的副本:

    public void OnButtonClick()
    
      //Here we now copy the complete object not just the reference to it
      var localCopyOfTheReference = (MyReference)someReference.Clone();

      var fireAndForget = Task.Run(async () =>
      
        await Task.Delay(30000);
        Send(localCopyOfTheReference);
      );
    

【讨论】:

【参考方案2】:

您可以通过以下方式启动一个任务,该任务将在每次单击按钮时延迟 30 秒发送参考。

public void OnButtonClick()

    var localCopyOfTheReference = someReference;
    var fireAndForget = Task.Run(async () =>
    
        await Task.Delay(30000);
        Send(localCopyOfTheReference);
    );

如果出现异常,就会吞噬异常。

【讨论】:

@ThEpRoGrAmMiNgNoOb 您的问题可能与someReference 字段有关。我更新了我的答案,以便在本地存储该字段的副本。 我试过你的代码,但问题仍然存在。它只发送 localCopyOfTheReference 的最新值。 嗯,someReference 是您问题的关键部分。您可能应该通过包含该字段的定义以及赋予其值的代码来更新您的问题。 好的,我会的。谢谢! 我为 Task.Delay 投票给这个答案 +1 是完成 30 秒计时器的最简单方法。谢谢你。【参考方案3】:

维护所有这些计时器似乎真的很令人困惑。

为什么不保留一份您希望事件发生的时间列表?并将事件设为委托。

ConcurrentDictionary<DateTime,Action> events = new ConcurrentDictionary<DateTime,Action>();

当有人点击某物时,

events.TryAdd(DateTime.Now.AddSeconds(30), () => HandleEvent(whatever, you, want));

在处理程序中,确保没有后续事件。

void HandleEvent()

    if (events.Keys.Any( dateKey => dateKey > DateTime.Now )) return;

    DoStuff();

现在你只需要一种方法来让事件发生。

timer.Elapsed += (s,e) => 
    var list = events.Where( item => item.Key <= DateTime.Now ).ToList();
    foreach (var item in list)
    
        item.Value.Invoke();
        events.TryRemove(item);
    
;

【讨论】:

以上是关于在并行运行 2 个或更多任务时保留变量值的主要内容,如果未能解决你的问题,请参考以下文章

在 Azure Devops 中使用从一个 PowerShell 任务到另一个任务的变量值

131 - 任务 05

Apache Airflow:在单个 DAG 运行中运行所有并行任务

进程切换 多线程并发

Python - 使用多处理并行处理受 CPU 限制的任务

Python - 使用多处理并行处理受 CPU 限制的任务