【问题标题】:tasks and global variables c#任务和全局变量c#
【发布时间】:2014-11-19 01:39:05
【问题描述】:

我有一个由事件处理程序启动的函数。在这个函数中,创建并运行了一个新任务来设置一个我稍后需要的全局变量,在另一个事件处理程序中。

这两个函数之间还有一些其他函数,它们不会更改其中使用的任何变量。但这就是它的样子。

private void EventWhereINeedTheGlobalVariableLater(object sender, Event e)
{
    //...work...

    need _variableIneed


    //....more work...
}


private void EventWhereISetGlobalVariable(object sender, Event e)
{
    //....work....
    //...make cancellationToken...
    //groups, isCommonalityGroup are other global variables already set. 

    Task.Factory.StartNew(() =>
    {
        // Clear variable to get new value
        _variableIneed = null;

        // Execute query to get new value
        _variableIneed = _workingManager.GetValue(groups, isCommonalityGroup, cancellationToken);

       RefreshView();

    }, cancellationToken);
}

我遇到了竞争条件,其中我需要的变量_variableIneed 在第二个事件处理程序中为空,而它不能为空。如果我没有飞过并尝试创建足够的事件来使 wpf 程序崩溃,它工作正常,但即使我这样做,我也需要它工作。

我可以做些什么来克服这些竞争条件吗?

我尝试使用.ContinueWithOnlyOnRanToCompletion 选项或其他选项。还有什么我可以尝试的吗?

**注意,我无法更改事件的排序/处理/处理方式。这是一个漂亮的石头设计,我只需要解决它并或多或少地保持它的样子。

**更新

我也尝试过将ParallelExtensionsExtrasOrderedTaskScheduler 类一起使用,但最终我仍然会得到我需要的变量的空引用。

【问题讨论】:

  • 如果在EventWhereISetGlobalVariable 处理程序中设置变量之前调用EventWhereINeedTheGlobalVariableLater 处理程序,您将无能为力。
  • 你的意思是你在EventWhereINeedTheGlobalVariableLater 中得到空值?可能是Task 没有完成?你需要同步它。
  • 是的@SriramSakthivel 我得到了我需要的全局变量的空值。
  • 这意味着EventWhereISetGlobalVariable 未被调用或StartNew 创建的任务尚未完成。您可能需要调用task.WaitawaitContinueWith 来同步访问。但在访问之前仍然可能存在竞争(另一个任务已启动并将_variableIneed 设置为null)。
  • @SriramSakthivel “可能会有比赛(另一个任务开始并设置......)这就是正在发生的事情。我能做些什么来解决这个问题?我试图避免使用task.wait如果可能的话。

标签: c# wpf scheduled-tasks


【解决方案1】:

当您有一个Task 来生成一个值时,不要将结果设置为全局变量,将该结果作为任务的Result,然后存储该任务。当其他一些代码稍后需要该结果时,它可以从任务中获取它。这将允许Task 类处理所有复杂的同步逻辑,防止在任务实际计算结果之前使用结果等。

当然,对于需要使用结果的事件,它可能不需要阻塞该任务,而是在任务完成后异步执行需要结果的其余代码。这可以通过在该任务上使用await 轻松完成。如果您只使用 .NET 4.0,那么您可以明确地使用 ContinueWith

【讨论】:

  • 那会是什么样子,结果是任务的Result,然后稍后从任务中访问它?
  • +1。使用 4.5 和 async 代码,或至少链式任务。
  • @Brett StartNew 的 lambda 应该是 return 结果,而不是设置另一个变量。然后,事件处理程序应该 await 您启动的 Task
  • 很抱歉在这里(可能)扔了一个扳手,但如果startnew lambda 中有另一个函数调用,这不会。会吗?我刚刚在问题中添加了另一个函数调用(我之前遗漏了它,我的错)。
  • @Brett 它需要做什么来计算结果并不重要。这里重要的是它如何在外部公开结果。
【解决方案2】:

使用 Servy 的方法 - async/Tasks。此答案仅用于娱乐目的,或者如果您不能使用 .Net 4.0+ 或 3.5 with Rx


由于您无法更改事件的顺序,因此您需要不时将变量视为null,或者防止它被视为null

一种选择是轮询此变量,并且仅在未设置为 null 时才起作用(如果您的 EventWhereINeedTheGlobalVariableLater 没有重复触发,则可能需要计时器)。

或者,您可以始终将值保留在变量中,或者阻止其他线程看到null 值。

防止null在成功情况下可见,如果“长计算”失败仍然可能为null:

   object lockObj = new object(); // at class level

   private void EventWhereISetGlobalVariable ...
   {
     lock(lockObj)
     {
       _variableIneed = null;
       // some long and convoluted computations
       _variableIneed = someResult;
     }
   }

   private void EventWhereINeedTheGlobalVariableLater(object sender, Event e)
   {
     lock(lockObj)
     {
      // unsing _variableIneed 
     }
   }

防止它被设置为null,仅当我们有一个值时才设置值(锁定对变量的访问或volatile 将起作用,更喜欢像在其他示例中那样锁定)。这是缓存一些需要很长时间来计算的值的常见模式,如果变量的用户看到稍微陈旧的值也没关系。

   volatile WhateverYourType _variableIneed;
   private void EventWhereISetGlobalVariable ...
   {
       // some long and convoluted computations
       _variableIneed = someResult ?? _variableIneed;
   }

注意:

  • 确保您了解如何处理代码中的任何锁定/同步 - 因此在多个地方使用该变量时要非常小心。考虑用锁包装对变量的访问。
  • 考虑将值复制到锁内的局部变量,并在“需要值”的代码中使用局部变量。否则其他线程可能会将其更改为新值/null。
  • 对于一次性设置变量,请考虑处理此类初始化的 Lazy<T> 类,或者如果使用 Cache 来存储值更合适。

【讨论】:

  • 我有一个问题 - 为将并行使用的变量锁定是否有意义?我的意思是如果我想从另一个线程获取或设置这个变量,它将被锁定
  • TPL 专门设计用于避免要求您执行此类操作,因为它们会增加代码的复杂性、潜在的错误等。这不是解决此问题的有效方法,因为他已经在使用 TPL。
  • @Sasha 锁可防止多个线程运行使用相同锁包裹的代码——因此其他线程将等到一个线程,并且只有一个线程离开使用lock 包裹的块。这将保证当前线程使用/设置/计算该值时,其他线程不会更改该值。考虑阅读lockCritical section
猜你喜欢
  • 2018-07-20
  • 2012-01-07
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 2015-10-04
  • 2010-10-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多