我有一个程序,它使用线程顺序执行耗时的进程.我希望能够监视每个线程的进度,类似于BackgroundWorker.ReportProgress / ProgressChanged模型的工作方式.由于我所处的其他限制,我无法使用ThreadPool或BackgroundWorker.允许/公开此功能的最佳方法是什么.重载Thread类并添加属性/事件?另一个更优雅的解决方案?
解决方法
Overload the Thread class and add a
property/event?
如果通过“超载”你实际上意味着继承然后没有.线程被密封,因此无法继承,这意味着您将无法向其添加任何属性或事件.
Another more-elegant solution?
创建一个封装将由线程执行的逻辑的类.添加可用于从中获取进度信息的属性或事件(或两者).
public class Worker { private Thread m_Thread = new Thread(Run); public event EventHandler<ProgressEventArgs> Progress; public void Start() { m_Thread.Start(); } private void Run() { while (true) { // Do some work. OnProgress(new ProgressEventArgs(...)); // Do some work. } } private void OnProgress(ProgressEventArgs args) { // Get a copy of the multicast delegate so that we can do the // null check and invocation safely. This works because delegates are // immutable. Remember to create a memory barrier so that a fresh read // of the delegate occurs everytime. This is done via a simple lock below. EventHandler<ProgressEventArgs> local; lock (this) { var local = Progress; } if (local != null) { local(this,args); } } }
更新:
让我更清楚一下为什么在这种情况下需要记忆障碍.屏障可防止在其他指令之前移动读取.最可能的优化不是来自cpu,而是来自JIT编译器“提升”while循环之外的Progress的读取.这种运动给人的印象是“陈旧”的阅读.这是一个问题的半现实演示.
class Program { static event EventHandler Progress; static void Main(string[] args) { var thread = new Thread( () => { var local = GetEvent(); while (local == null) { local = GetEvent(); } }); thread.Start(); Thread.Sleep(1000); Progress += (s,a) => { Console.WriteLine("Progress"); }; thread.Join(); Console.WriteLine("Stopped"); Console.ReadLine(); } static EventHandler GetEvent() { //Thread.MemoryBarrier(); var local = Progress; return local; } }
必须在没有vshost进程的情况下运行Release构建.任何一个都将禁用显示错误的优化(我相信这在框架版本1.0和1.1中也不可重现,因为它们更原始的优化).错误是“Stopped”永远不会显示,即使它显然应该是.现在,取消对Thread.MemoryBarrier的调用,并注意行为的变化.还要记住,即使对此代码结构进行的最微妙的更改,也会阻止编译器进行优化的能力.一个这样的改变是实际调用委托.换句话说,您目前无法使用空检查后跟调用模式重现陈旧的读取问题,但CLI规范(我知道无论如何)中没有任何内容禁止未来假设的JIT编译器重新应用“提升”优化.