我有一个服务负责许多任务,其中一个是在一个单独的线程(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(如果是这样的话?)......
答案 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)
答案 4 :(得分:0)
我会使用委托/事件从线程到调用者。如果调用者是UI或那些行上的某个地方,我会很好地使用消息泵并使用适当的Invoke()来在需要时使用UI线程序列化通知。
答案 5 :(得分:0)
我曾经写过一个应用程序,需要一个标记来显示一个主题所取得的进展。我只是在它们之间使用了共享的全局变量。父将只读取值,线程只会更新它。无需同步,因为只有父级读取它,并且只有子级以原子方式写入它。事情发生的时候,父母正在经常重绘东西,以至于当孩子更新变量时,甚至不需要孩子戳。有时最简单的方法也很有效。
答案 6 :(得分:0)
您当前的代码会混合使用JOB_STATUS
的字符串和整数,这不起作用。我在这里假设字符串,但这并不重要,我会解释。
当前实现是线程安全的,因为不会发生内存损坏,因为所有对引用类型字段的赋值都保证是原子的。 CLR要求这样做,否则如果你能以某种方式访问部分更新的引用,你可能会访问非托管内存。但是,您的处理器可以免费为您提供原子性。
因此,只要您使用类似字符串的引用类型,就不会出现任何内存损坏。对于像int(和更小)这样的原语和基于它们的枚举也是如此。 (只是避免使用long和更大的非原始值类型,例如可以为空的整数。)
但是,这不是故事的结尾:不保证此实现始终代表当前状态。原因是调用GetStatus
的线程可能正在查看JOB_STATUS
字段的陈旧副本,因为SetState
中的赋值不包含所谓的内存屏障。也就是说:JOB_STATUS
的新值不需要立即发送到您的主RAM。有几个原因可以推迟:
JOB_STATUS
的值存储在寄存器中。同样,寄存器的使用效率远远高于主RAM。但是,这确实意味着它可能不会足够早地看到更改,因为它仍然在查看寄存器中的旧副本。 (我们不是在这里谈论会议纪要,但仍然。)所以,如果你想100%确定每个线程&处理器核心立即知道已更改的状态,将您的字段声明为volatile
:
private volatile int JOB_STATUS;
现在,没有任何锁定结构的GetStatus/SetStatus
是真正的线程安全的,因为volatile
要求从主RAM 中立即读取和写入值(或者某些东西) 100%等效,如果处理器可以更有效地做到这一点。)
请注意,如果您未将字段声明为volatile
,则必须使用同步原语,例如lock
,但一般来说,您需要使用同步原语Get 和< / em>设置,否则您将无法解决volatile
修复的问题。
请注意,当您正在进行IPC调用以获取状态时,考虑到IPC调用的开销,我打赌您不会真正能够观察到非易失性和易失性之间的任何差异。毫无疑问,线程同步在幕后进行。
有关volatile
的详细信息,请参阅MSDN上的volatile (C#)。