子线程如何通知父线程其状态/进度?

时间:2009-09-04 20:36:41

标签: c#

我有一个服务负责许多任务,其中一个是在一个单独的线程(threadJob子)上启动工作(一次一个),这些工作可能需要相当长的时间和

我们需要报告各种各样的阶段。

通常,调用应用程序从服务(GetStatus)请求状态,这意味着服务需要知道作业(子线程)在什么时候

at,我希望在一些里程碑上,子线程可以以某种方式通知(SetStatus)父线程(服务)其状态,并且服务可以返回该信息

到调用应用程序。

例如 - 我希望做这样的事情:

class Service
{
private Thread threadJob;
private int JOB_STATUS;

public Service()
    {
    JOB_STATUS = "IDLE";
    }

public void RunTask()
    {
    threadJob = new Thread(new ThreadStart(PerformWork));
    threadJob.IsBackground = true;
    threadJob.Start();
    }

public void PerformWork()
    {
    SetStatus("STARTING");
    // do some work //
    SetStatus("PHASE I");
    // do some work //
    SetStatus("PHASE II");
    // do some work //
    SetStatus("PHASE III");
    // do some work //
    SetStatus("FINISHED");
    }

private void SetStatus(int status)
    {
    JOB_STATUS = status;
    }

public string GetStatus()
    {
    return JOB_STATUS;
    }

};

因此,当需要执行一个作业时,会调用RunTask()并启动该线程(threadJob)。这将运行并执行一些步骤(使用SetStatus在

设置新状态

各种要点)并最终完成。现在,还有函数GetStatus(),它应该在需要时返回STATUS(来自使用IPC的调用应用程序) - 这个状态

应该反映threadJob运行的作业的当前状态。

所以,我的问题很简单...... threadJob(或者更具体地说是PerformWork())如何以线程安全的方式返回Service状态的变化(我假设我上面的SetStatus / GetStatus示例是

不安全)?我需要使用活动吗?我假设我不能简单地直接更改JOB_STATUS ...我应该使用LOCK(如果是这样的话?)......

7 个答案:

答案 0 :(得分:8)

您可能已经对此进行了调查,但BackgroundWorker类为您提供了一个在后台线程上运行任务的良好界面,并为进度已更改的通知提供了挂钩事件。

答案 1 :(得分:2)

我让子线程引发'statusupdate'事件,传递一个包含父级所需信息的结构,并让父级在启动它时订阅它。

答案 2 :(得分:2)

您可以在Service类中创建一个事件,然后以线程安全的方式调用它。请密切关注我如何实现SetStatus方法。

class Service
{
  public delegate void JobStatusChangeHandler(string status);

  // Event add/remove auto implemented code is already thread-safe.
  public event JobStatusChangeHandler JobStatusChange;

  public void PerformWork()
  {
    SetStatus("STARTING");
    // stuff
    SetStatus("FINISHED");
  }

  private void SetStatus(string status)
  {
    JobStatusChangeHandler snapshot;
    lock (this)
    {
      // Get a snapshot of the invocation list for the event handler.
      snapshot = JobStatusChange;
    }

    // This is threadsafe because multicast delegates are immutable.
    // If you did not extract the invocation list into a local variable then
    // the event may have all delegates removed after the check for null which
    // which would result in a NullReferenceException when you attempt to invoke
    // it.
    if (snapshot != null)
    {
      snapshot(status);
    }
  }
}

答案 3 :(得分:1)

您可以使用Event-Based Async Pattern

答案 4 :(得分:0)

我会使用委托/事件从线程到调用者。如果调用者是UI或那些行上的某个地方,我会很好地使用消息泵并使用适当的Invoke()来在需要时使用UI线程序列化通知。

答案 5 :(得分:0)

我曾经写过一个应用程序,需要一个标记来显示一个主题所取得的进展。我只是在它们之间使用了共享的全局变量。父将只读取值,线程只会更新它。无需同步,因为只有父级读取它,并且只有子级以原子方式写入它。事情发生的时候,父母正在经常重绘东西,以至于当孩子更新变量时,甚至不需要孩子戳。有时最简单的方法也很有效。

答案 6 :(得分:0)

您当前的代码会混合使用JOB_STATUS的字符串和整数,这不起作用。我在这里假设字符串,但这并不重要,我会解释。


当前实现是线程安全的,因为不会发生内存损坏,因为所有对引用类型字段的赋值都保证是原子的。 CLR要求这样做,否则如果你能以某种方式访问​​部分更新的引用,你可能会访问非托管内存。但是,您的处理器可以免费为您提供原子性。

因此,只要您使用类似字符串的引用类型,就不会出现任何内存损坏。对于像int(和更小)这样的原语和基于它们的枚举也是如此。 (只是避免使用long和更大的非原始值类型,例如可以为空的整数。)

但是,这不是故事的结尾:不保证此实现始终代表当前状态。原因是调用GetStatus的线程可能正在查看JOB_STATUS字段的陈旧副本,因为SetState中的赋值不包含所谓的内存屏障。也就是说:JOB_STATUS的新值不需要立即发送到您的主RAM。有几个原因可以推迟:

  1. 写入主RAM本身就很慢(相对而言),这就是你的处理器首先拥有各种缓冲区和L-something缓存的原因,因此处理器通常会延迟内存同步。不会很久,但可能会延迟。这在多核处理器上非常明显,因为它们通常每个核心都有单独的缓存。
  2. 作为某种优化策略的一部分,JIT可能早先将JOB_STATUS的值存储在寄存器中。同样,寄存器的使用效率远远高于主RAM。但是,这确实意味着它可能不会足够早地看到更改,因为它仍然在查看寄存器中的旧副本。 (我们不是在这里谈论会议纪要,但仍然。)
  3. 所以,如果你想100%确定每个线程&处理器核心立即知道已更改的状态,将您的字段声明为volatile

    private volatile int JOB_STATUS;
    

    现在,没有任何锁定结构的GetStatus/SetStatus是真正的线程安全的,因为volatile要求从主RAM 中立即读取和写入值(或者某些东西) 100%等效,如果处理器可以更有效地做到这一点。)

    请注意,如果您未将字段声明为volatile,则必须使用同步原语,例如lock,但一般来说,您需要使用同步原语Get 和< / em>设置,否则您将无法解决volatile修复的问题。

    请注意,当您正在进行IPC调用以获取状态时,考虑到IPC调用的开销,我打赌您不会真正能够观察到非易失性和易失性之间的任何差异。毫无疑问,线程同步在幕后进行。

    有关volatile的详细信息,请参阅MSDN上的volatile (C#)